Notxor tiene un blog

Defenestrando la vida


Interacción con el usuario y widgets

Interacción básica

Ya vimos cómo se podían hacer funciones que nos sirvieran de comandos y que se pudieran hacer de modo interactivo. Pero si la interacción se limita a llamar a una función, es posible que nos quedemos cortos, porque lo que necesitan los programas para funcionar son datos y la mayor fuente de datos es el propio usuario.

La interacción más sencilla es informar de algo al usuario. Para esos menesteres utilizamos la función message, que ya ha salido alguna vez en este minicurso. Sin embargo, message tiene algunas variantes que pueden servirnos para otras cosas. Por ejemplo, vamos a ver las diferencias en los siguientes mensajes:

(message "Esto es un mensaje que informa al usuario de que el código hace algo.")
(message-box "Esto también informa, pero casi salta a la vista.")
(message-or-box "Esto también lo hace pero además elige cómo hacerlo.")
(message-y-or-n-p "Con este mensaje podemos responder además." t)
(error "¡Se ha producido un error!")

Podemos ver cuatro formas de enviar un mensaje. La primera es la más sencilla, sólo informa de algo sin más. Además, como lo hace en el área de eco, desaparecerá a los pocos segundos y es posible que al usuario se le pase y no llegue a leerlo. Si estamos en un entorno gráfico, podemos emplear la segunda forma message-box, que mostrará el mensaje en un diálogo con un botón de aceptar. La tercera forma dependerá de nuestra configuración y entorno, puede mostrarse en forma de diálogo o en el área de eco. Y por último, la cuarta alternativa nos permite mostrar un mensaje y esperar una respuesta. Si el usuario pulsa y la forma devolverá t y si pulsa n, devolverá nil.

La forma error envía un mensaje y abre un buffer *Backtrace* y lo podemos utilizar como una forma de detener el programa informando al usuario de lo que ha pasado.

Para las peticiones de confirmación además, emacs nos proporciona dos formas más: y-or-n-p y yes-or-no-p. Básicamente hacen lo mismo que message-y-or-n-p pero con sutiles diferencias:

(y-or-n-p "¿Es mejor que te pregunten antes de hacer algo?")
(yes-or-no-p "¿Escribir más implica menos accidentes?")

La primera forma, se contesta como ya se ha explicado, pulsando y o n. Sin embargo, la segunda forma solicita que se escriba de modo completo yes o no. El manual recomienda utilizar esta función cuando se vaya a realizar alguna acción irreversible, como por ejemplo el borrado de un fichero o un texto.

Otras veces necesitamos que el usuario elija entre varias alternativas, por ejemplo:

(completing-read "Elige fruta: " '("manzana" "naranja" "pera" "plátano"))
(completing-read "Introduce un número: " nil)

Como vemos, completing-read abre más aún la interacción con el usuario. En la primera forma del ejemplo, vemos que podemos dar una lista de alternativas para seleccionar entre ellas. Esas alternativas podemos darlas en cualquier orden, la forma las ordenará alfabéticamente. Además podemos solicitar que el usuario proporcione cualquier entrada, en el ejemplo se solicita un número. Al introducirlo nos daremos cuenta que la función siempre devuelve una cadena. Así que si lo que deseamos es un número deberíamos convertir la entrada:

(string-to-number
  (completing-read "Introduce un número: " nil))

Widgets

widgets nos proporciona una forma de interacción más completa con el usuario: nos permite crear formularios. Los widgets son chismáticos que nos sirven para establecer datos en esos formularios.

Los tipos de widgets básicos que podemos trabajar son los siguientes:

link
Es un área de texto asociado con una acción. Se muestran como hipertexto en el texto.
push-button
Es un link dibujado como un botón.
editable-field
Un campo de texto editable que puede tener un tamaño variable o fijo.
menu-choice
Permite seleccionar una opción de un menú de opciones múltiples. Cada opción es a su vez un widget. Sólo es visible una opción en el buffer.
radio-button-choice
Permite al usuario seleccionar una entre múltiples opciones activando los radio botones. Las opciones se implementan también como widgets.
item
Un widget para utilizarlo en menu-choice y radio-button-choice.
toggle
Un interruptor de tipo on-off.
checkbox
Una caja para marcar, de forma [ ] - [X].
editable-list
Una lista editable donde el usuario puede añadir y borrar ítems, cada uno de los cuales es también un widget.

¿Complicado? Vamos a hacer un ejemplo sencillo para que podamos hacernos una idea de su potencia.

(require 'widget) ; Declaramos que necesita «widget» para funcionar

(eval-when-compile
  (require 'wid-edit)) ; Y si lo compilamos, necesita también «wid-edit»

(defvar lista-campos '()
  "Una variable que utilizaremos para devolver el contenido del formulario.")

(defvar lista-resultados '()
  "Una variable en la que almacenaremos los resultados.")

(defun formulario-datos ()
  "Función que dibuja nuestro formulario en un buffer."
  (interactive)                                            ; Ya sabéis, para poder llamarla desde «M-x»
  (switch-to-buffer "*Formulario de datos*")               ; Nos creamos un buffer
  (kill-all-local-variables)                               ; Nos cargamos todas las variables locales que haya de antes.
  (make-local-variable 'lista-campos)                      ; hacemos local la variable donde guardaremos en contenido.
  (let ((inhibit-read-only t))                             ; Inhibir el «sólo lectura»
    (erase-buffer))                                        ; Poner el buffer en blanco para escribir en él.
  (widget-insert "Esto es un texto que podemos utilizar para explicar para qué es el formulario.\n\n")
  (setq lista-campos                                       ; Añadir el siguiente campo a la lista
        (cons (widget-create 'editable-field               ; Campo de edición para el nombre
                             :size 25                      ; Tamaño en caracteres del campo
                             :format "Nombre:     %v "     ; Dejar espacio para el texto tras el campo
                             "Mi nombre")                  ; Etiqueta del campo
              lista-campos))
  (setq lista-campos
        (cons (widget-create 'menu-choice                  ; Un menú para seleccionar género
                             :tag "Género"                 ; Etiqueta del campo
                             :value "Femenino"             ; Valor por defecto
                             '(item "No binario") '(item "Femenino") '(item "Masculino"))
              lista-campos))
  (setq lista-campos
        (cons (widget-create 'editable-field               ; Campo de edición para el primer apellido
                             :size 50
                             :format "Apellido 1: %v"
                             "Primer Apellido")
              lista-campos))
  (setq lista-campos
        (cons (widget-create 'editable-field               ; Campo de edición para el segundo apellido
                             :size 50
                             :format "\nApellido 2: %v \n"
                             "Segundo Apellido")
              lista-campos))
  (widget-insert "\n")                                     ; Separador de la línea de botones
  (widget-create 'push-button
                 :notify (lambda (&rest ignore)
                           (dolist (elemento lista-campos lista-resultados)
                             (message-box (widget-value elemento))))
                 "Aceptar")
  (widget-insert " ")                                      ; Separador entre botones
  (widget-create 'push-button
                 :notify (lambda (&rest ignore)
                           (kill-buffer "*Formulario de datos*"))
                 "Cerrar Formulario")
  (use-local-map widget-keymap)                            ; Activar la edición de los campos
  (widget-setup))

He puesto bastantes comentarios para saber qué hace cada cosa. Básicamente lo que hace el código es establecer un par de listas, una de campos y otra de resultados. Se crean los widgets y luego se muestra el formulario.

Es de destacar el diferente comportamiento de algunos widgets según se acceda desde teclado o con el ratón, por ejemplo en el menu-choice. En el primer caso se activará un buffer-popup con las alternativas mientas que en el segundo aparecerá un menú gráfico.

Además, los widgets básicos se pueden combinar en widgets más complejos, pero de momento no nos meteremos en esto.

Funciones lambda

A destacar también los :notify de los dos botones que hemos colocado al final del formulario. En general, las funciones se definen con defun tomando así un nombre con el que llamar a la forma. Sin embargo, en algunas situaciones las funciones no necesitan ser llamadas y se puede emplear una función anónima que se definen como se ve en el código:

(lambda (argumentos)
  "Documentación en forma de cadena."
  cuerpo-de-la-funcion)

En el modo en que se utilizan en el código, se lanzarán cada vez que se pulse en el botón.


Comentarios

Debido a algunos ataques mailintencionados a través de la herramienta de comentarios, he decidido no proporcionar dicha opción en el Blog. Si alguien quiere comentar algo, me puede encontrar en Mastodon y en Diaspora con el nick de Notxor.

También se ha abierto un grupo en Telegram, para usuarios de esa red.

Disculpen las molestias.