Diálogos con Python

Este es uno de los capítulos del tutorial Diálogos para scripts. Encontrarás los enlaces a todos los de capítulos, al final de este artículo.

Hasta el momento, en los diferentes capítulos del tutorial, has ido viendo distintas herramientas para crear diálogos para tus scripts en Bash. Bueno, en Bash o en cualquier otro lenguaje con el que quieras trabajar. En este nuevo capítulo del tutorial, el enfoque es un tanto distinto a lo que has visto hasta el momento. La cuestión es que no utilizarás una herramienta como kdialog, yad o zenity. Vas a utilizar directamente Python y las librerías necesarias. Este, podría ser un buen punto de partida, para comenzar a desarrollar con Python, si es que no lo conoces. O en el caso de que no conozcas Gtk+ y si conozcas Python, a o mejor esto es una oportunidad de comenzar con ello. De cualquier forma, el objeto de este capítulo, es guiarte para que puedas crear tus propios diálogos con Python.

Al igual que has visto en los anteriores capítulos, el objetivo es que puedas crear una interfaz gráfica para tus scripts en Bash o en el lenguaje que tu quieras, con independencia de que los diálogos utilizan Python. Esto, podría llevarte a pensar que a lo mejor también tendrías que hacer tus scripts en Python, pero nada mas lejos de la realidad. Incluso te diría, que es posible que esto te de pie a crear una base de herramientas con las que crear tus propios diálogos, pero esto ya te lo iré comentando a lo largo del capítulo.

Diálogos con Python

Diálogos con Python

Un punto de partida para tus diálogos con Python

Antes de que te metas de lleno con esto de los diálogos en Python, quería comentarte que al contrario que has visto con las otras herramientas, para crear diálogos, en este caso es algo distinto.

En este caso vas a utilizar los diferentes módulos que hay disponibles para Python para crear esos diálogos con Python. Esto probablemente te lleve a que esos scripts serán un poco mas largos e incluso te de para crear dos archivos, uno con el interfaz gráfico, lo que es el diálogo, y un segundo con el script en si.

Instalación

Lo siguiente es instalar Python 3. En el caso de Ubuntu Python 3 viene instalado por defecto. Y en el caso concreto de Ubuntu 20.04 tienes la versión 3.8.2. Esto lo puedes ver fácilmente ejecutando la instrucción python --version.

En general, tampoco es necesario que instales los módulos necesarios para todo lo que vas a ver en este capítulo, porque habitualmente está instalado. Pero en cualquier caso, para el caso de Debian, Ubuntu, etc, tienes que ejecutar la siguiente instrucción en un terminal,

sudo apt install python3-gi python3-gi-cairo gir1.2-gtk-3.0

Un diálogo sencillo

Como en el resto de capítulos de este tutorial, hay que empezar por un diálogo sencillo. Un diálogo que muestre simplemente un mensaje. Esto lo puedes hacer con estas dos líneas,

from gi.repository import Gtk
Gtk.MessageDialog(text='Este es el mensaje que se muestra').run()

Sin embargo, si lo haces de esta forma verás que se muestra un mensaje de aviso, que te indica que no se ha comprobado la versión que se importa, haciendo referencia a Gtk. Y por otro lado, en el caso de que salgas del script utilizando Ctrl+c, te mostrará un mensaje de error. Para evitar estos dos inconvenientes, yo utilizaré el siguiente script,

import gi
try:
    gi.require_version('Gtk', '3.0')
except ValueError:
    exit(1)
from gi.repository import Gtk


if __name__ == '__main__':
    dialog = Gtk.MessageDialog(message_type=Gtk.MessageType.ERROR,
                               text='Este es el mensaje que se muestra')
    dialog.set_title('https://www.atareao.es')
    try:
        dialog.run()
    except KeyboardInterrupt:
        pass

En la primera parte se comprueba la versión de Gtk que se importa, y en el caso de que no sea la correcta saldrá, o también en el caso de que Gtk no esté disponible.

Por otro lado, igualmente, he añadido un try-catch para la ejecución del diálogo, para que en el caso de que se interrumpa con un Ctrl+c no se muestre el error.

Informando al usuario

Algunos detalles

Quería resaltar algunos detalles y diferencias adicionales entre el primer ejemplo, y este segundo. Lo primero es que he incluido el parámetro message_type. Esto lo que hace es mostrar o no mostrar, un icono en la parte izquierda del cuadro de diálogo. Tienes estos tipos de mensajes,

  • ERROR para mensajes relativos a errores
  • INFORMATION para informar al usuario
  • OTHER en el caso de que no quieras mostrar icono
  • QUESTION para preguntar al usuario en referencia a algo
  • WARNING en el caso de que quieras avisar al usuario

Por otro lado, puedes definir el título del diálogo utilizando el método set_title y puedes dar estilo al mensaje utilizando la opción set_markup.

Mensajes básicos

Preguntando al usuario

En el caso de que quieras preguntar al usuario, tendrías que añadir botones para permitir que el usuario pueda dar una respuesta en base al botón pulsado. Por ejemplo,

dialog = Gtk.MessageDialog()
dialog.set_title('https://www.atareao.es')
dialog.set_markup('¿Estás <span weight="bold" foreground="red">seguro</span>?')
dialog.add_button('Si', Gtk.ResponseType.YES)
dialog.add_button('No', Gtk.ResponseType.NO)
try:
    if dialog.run() == Gtk.ResponseType.YES:
        exit(0)
except KeyboardInterrupt:
    pass
exit(1)
Dando estilos a tus diálogos con Python

Esto lo podrías utilizar igual que viste en el capítulo anterior en un script de Bash. Por ejemplo,

python3 ejemplo_03.py
ans=$?
if [ $ans -eq 0 ]
then
    echo "Si que quiere continuar"
else
    echo "No quiere continuar"
fi

Solicitar información

Si quieres ir un paso mas allá y preguntar al usuario por algún dato, como podría ser su nombre, tienes que utilizar un cuadro de diálogo mas general que este que has visto hasta el momento. Por ejemplo,

dialog = Gtk.Dialog.new()
dialog.set_title('https://www.atareao.es')
dialog.add_button('Si', Gtk.ResponseType.YES)
dialog.add_button('No', Gtk.ResponseType.NO)
box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 10)
box.set_margin_top(10)
box.set_margin_bottom(10)
dialog.get_content_area().add(box)
label = Gtk.Label.new('Dime tu nombre')
box.pack_start(label, True, True, 5)
entry = Gtk.Entry.new()
box.pack_start(entry, True, True, 5)
dialog.show_all()
try:
    if dialog.run() == Gtk.ResponseType.YES and entry.get_text():
        print(entry.get_text())
        exit(0)
except KeyboardInterrupt:
    pass
exit(1)

Este es básicamente igual que el anterior, pero he añadido dos objetos adicionales. Por un lado el objeto Label que permite escribir etiquetas de texto o textos. Por otro lado, Entry, que permite que el usuario introduzca el texto que quiera.

Un cuestionario básico

Igual que en el caso anterior, puedes llamar a este script desde otro script, tal y como te demuestro a continuación,

nombre=$(python3 ejemplo_04.py)
ans=$?
if [ $ans -eq 0 ]
then
    echo "Tu nombre es ${nombre}"
else
    echo "No me ha querido decir el nombre"
fi

En este caso tienes tres nuevos objetos,

  • Box es el contenedor donde vas a añadir los otros dos objetos.
  • Label sirve para mostrar etiquetas
  • Entry para introducir texto.

En el caso de que el usuario haya introducido un texto en Entry y además haya pulsado Si el resultado será 0 y devolverá el contenido.

Preguntando usuario y contraseña… por ejemplo

¿Que sucede si quieres preguntar varias datos en el mismo formulario? En ese caso, en lugar de utilizar el objeto Box tienes que utilizar el objeto Grid que te va a dar muchas mas posibilidades. Así, por ejemplo, si quieres preguntar al usuario por nombre y contraseña,

dialog = Gtk.Dialog.new()
dialog.set_title('https://www.atareao.es')
dialog.add_button('Si', Gtk.ResponseType.YES)
dialog.add_button('No', Gtk.ResponseType.NO)
grid = Gtk.Grid.new()
grid.set_margin_top(10)
grid.set_margin_bottom(10)
grid.set_margin_start(10)
grid.set_margin_end(10)
grid.set_column_spacing(10)
grid.set_row_spacing(10)
dialog.get_content_area().add(grid)
grid.attach(Gtk.Label.new('Nombre:'), 0, 0, 1, 1)
nombre = Gtk.Entry.new()
grid.attach(nombre, 1, 0, 1, 1)
grid.attach(Gtk.Label.new('Contraseña:'), 0, 1, 1, 1)
password = Gtk.Entry.new()
password.set_visibility(False)
grid.attach(password, 1, 1, 1, 1)
dialog.show_all()
try:
    if dialog.run() == Gtk.ResponseType.YES and \
            nombre.get_text() and \
            password.get_text():
        print('{}|{}'.format(nombre.get_text(), password.get_text()))
        exit(0)
except KeyboardInterrupt:
    pass
exit(1)

En el caso del objeto Grid habrás observado que he añadido algunas opciones adicionales, mas que nada para facilitar la visibilidad del formulario. En este caso he añadido las opciones,

  • set_column_spacing define el espacio entre columnas
  • set_row_spacing idéntico al anterior pero sirve para definir el espacio entre filas

Por otro lado, para el caso del objeto Entry he utilizado el método set_visibility para que en lugar de mostrar los caracteres conforme vas escribiendo se muestre un punto.

Preguntando al usuario

Queda como punto final el hecho de devolver los resultados. En este caso, lo que he hecho es devolverlos separados por un pipe, para que luego sea mas fácil de leer desde un script en Bash. Un script en Bash, como el que te muestro a continuación,

data=$(python3 ejemplo_05.py)
ans=$?
if [ $ans -eq 0 ]
then
    IFS='|' read -ra data <<<${data}
    nombre=${data[0]}
    password=${data[1]}
    echo "Tu nombre es ${nombre}"
    echo "Tu contraseña es ${password}"
else
    echo "No me ha querido responder"
fi

Por supuesto, que podrías hacerlo todo directamente en Python. Pero aquí tienes las dos opciones, por si quieres mantener tus scripts en Bash, y por ejemplo, quieres crear tus propios módulos en Python, para utilizarlos en diferentes scripts.

Preguntando cualquier cosa…

A partir de aquí, y una vez has visto el ejemplo anterior, ya te puedes imaginar que crear diferentes cuadros de diálogo es realmente sencillo. Tan solo tienes que elegir el objeto necesario. Así, la estructura general del cuadro de diálogo sería algo como lo que te muestro a continuación,

dialog = Gtk.Dialog.new()
dialog.set_title('https://www.atareao.es')
dialog.add_button('Si', Gtk.ResponseType.YES)
dialog.add_button('No', Gtk.ResponseType.NO)
grid = Gtk.Grid.new()
grid.set_margin_top(10)
grid.set_margin_bottom(10)
grid.set_margin_start(10)
grid.set_margin_end(10)
grid.set_column_spacing(10)
grid.set_row_spacing(10)
dialog.get_content_area().add(grid)
# === inicio contenido ===
# ==== fin  contenido ====
dialog.show_all()
try:
    if dialog.run() == Gtk.ResponseType.YES:
        # === inicio salida ===
        # ==== fin  salida ====
        # 
        exit(0)
except KeyboardInterrupt:
    pass
exit(1)

Entre los dos comentarios de inicio contenido y fin contenido es donde deberías añadir todos los campos que necesites para pedir información al usuario. Mientras que entre los comentarios inicio salida y fin salida, es donde pondrías la salida formateada.

Elegir entre diferentes opciones

En este caso, tienes que utilizar un objeto del tipo ComboBox en combinación con un ListStore, por ejemplo. La solución, no es tan sencilla o tan fácil de implementar como en ejemplos anteriores, pero tampoco es nada del otro mundo. Así, únicamente te dejo la parte del contenido y de la salida, el resto del contenido lo puedes encontrar en el repositorio.

Así, el contenido será como te muestro a continuación,

grid.attach(Gtk.Label.new('Selecciona un componente:'), 0, 0, 1, 1)
model = Gtk.ListStore(str)
model.append(['Jamón'])
model.append(['Queso'])
model.append(['Huevo'])
componente = Gtk.ComboBox.new_with_model(model)
renderer_text = Gtk.CellRendererText()
componente.pack_start(renderer_text, True)
componente.add_attribute(renderer_text, 'text', 0)
componente.set_active(0)
grid.attach(componente, 1, 0, 1, 1)

En esta parte te tienes que fijar en que es necesario utilizar un objeto, en este caso CellRendererText para modelizar lo que se muestra en el ComboBox, añadirlo, y definir el atributo de lo que se muestra. Además, he indicado, cual es el valor que se mostrará por defecto con el método set_active.

Para la salida, he utilizado lo siguiente,

print(model.get_value(componente.get_active_iter(), 0))

Esto nos permite encontrar cual es el elemento que está activo en el ComboBox y sacarlo del modelo con el método get_value.

Selección de un elemento utilizando tus dialogos con Python

Si en lugar de mostrar solamente el nombre del componente, quisieras mostrar también las calorías del mismo, tendrías que hacer los siguientes cambios,

model = Gtk.ListStore(str, int)
model.append(['Jamón', 100])
model.append(['Queso', 100])
model.append(['Huevo', 200])
componente = Gtk.ComboBox.new_with_model(model)
renderer_text = Gtk.CellRendererText()
componente.pack_start(renderer_text, True)
componente.add_attribute(renderer_text, 'text', 0)
renderer_caloria = Gtk.CellRendererText()
componente.pack_start(renderer_caloria, True)
componente.add_attribute(renderer_caloria, 'text', 1)

Quizá este objeto sea de los mas complejos que existen porque necesitas de mas de un elemento para poder trabajar con él. Pero, como te puedes imaginar, esta complejidad que ofrece, también te permite tener mayores posibilidades.

Selección de un combo

Seleccionar archivos

Una vez visto el ejemplo anterior, seguro que este que te muestro a continuación te va a resultar mucho mas sencillo. En este caso, para seleccionar un archivo, tan solo tienes que utilizar algo como lo que te muestro a continuación,

dialog = Gtk.FileChooserDialog()
dialog.set_title('https://www.atareao.es')
dialog.add_button('Si', Gtk.ResponseType.YES)
dialog.add_button('No', Gtk.ResponseType.NO)
try:
    if dialog.run() == Gtk.ResponseType.YES:
        print(dialog.get_filename())
        exit(0)
except KeyboardInterrupt:
    pass
exit(1)

El objeto FileChooserDialog, tiene multitud de opciones, que te permitirán utilizar filtros a la hora de elegir archivos o también, por ejemplo, seleccionar varios elementos.

Selección de archivos y directorios

Seleccionar tipografías

Otro diálogo realmente sencillo es el que te permite selecciona tipografías. Se trata de cuadros de diálogo que están preparados para su uso, sin prácticamente realizar ninguna modificación sobre ellos. Así, por ejemplo,

Selección de tipografías con tus diálogos con Python
dialog = Gtk.FontChooserDialog()
try:
    if dialog.run() == Gtk.ResponseType.OK:
        print(dialog.get_font())
        exit(0)
except KeyboardInterrupt:
    pass
exit(1)

Seleccionar colores

Otro ejemplo de cuadro de diálogo que viene preparado para utilizar recién sacado de la caja es el ColorChooserDialog. Así, crear un diálogo para seleccionar un color es tan sencillo como en el caso del ejemplo que muestro a continuación,

Seleccionando colores
dialog = Gtk.ColorChooserDialog()
try:
    if dialog.run() == Gtk.ResponseType.OK:
        print(dialog.get_rgba())
        exit(0)
except KeyboardInterrupt:
    pass
exit(1)

Seleccionar una fecha

En este caso, no existe un cuadro de diálogo preparado para que el usuario seleccione una fecha. En este caso, es necesario confeccionarlo a medida, pero puedes seguir los criterios marcados en este tutorial sobre diálogos en Python. Tampoco es tan complejo, sería algo similar a lo que hemos visto en alguno de los ejemplos anteriores, pero, en este caso, utilizando el objeto Gtk.Calendar, tal y como puedes ver a continuación,

Un calendario con tus diálogos con Python
fecha = Gtk.Calendar.new()
grid.attach(fecha, 1, 0, 1, 1)
dialog.show_all()
try:
    if dialog.run() == Gtk.ResponseType.YES:
        print(fecha.get_date())
        exit(0)
except KeyboardInterrupt:
    pass
exit(1)

Conclusiones

Aquí no he querido añadir un ejemplo adicional para los formularios, porque simplemente es coger uno de los ejemplos anteriores, y aplicar los diferentes objetos que has podido ver a lo largo de este capítulo del tutorial.

Tu mismo habrás llegado a la conclusión, de que hacer tus diálogos con Python, no es tan sencillo e intuitivo como con las herramientas que has podido ver hasta el momento, pero sin embargo, te da mayores opciones y posibilidades.


Imagen de portada de Tamara Gore en Unsplash

Deja una respuesta

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