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
yradio-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