4.5 Diálogos, Stock Items y barras de Progreso

Un diálogo es un ejemplo de widget compuesto. Consta de una ventana, una parte superior que es una caja vertical, y un área de acción que es una caja horizontal que suele constar de uno o varios botones. Normalmente ambas partes están separadas por un separador horizontal.

El widget Dialog puede usarse para mensajes pop-up (surgen de la aplicación) al usuario, u otras tareas similares. Las funciones básicas necesarias son:

dialogNew :: IO Dialog

dialogRun :: DialogClass self => self -> IO ResponseID

Puedes añadir botones al area de acción con:

dialogAddButton :: DialogClass self => self -> String -> ResponseId -> IO Button

Cualquier widget puede ser añadido de un modo semejante con dialogAddActionWidget.

El String en dialogAddButton puede ser el texto del botón, pero como los diálogos se suelen usar en situaciones estándar, un StockItem suele ser más apropiado.

Los StockItems son recursos conocidos en Gtk2Hs, como los IconSets estándar. Puedes definir los tuyos, pero hay muchos predefinidos en el módulo Graphics.UI.Gtk.General.StockItems. Tienen un identificador, StockId, que es un tipo sinónimo de String. Con este identificador, un widget (normalemente un botón) con el texto y el icono apropiados se selecciona automáticamente.

Si usas un StockId al añadir un botón al diálogo, puedes usar un constructor ResponseId predefinido con los botones. (ResponseId no es un String.) Las respuestas pueden construirse con ResponseUser Int.

Siempre que se pulsa un botón de diálogo, su respuesta se pasa a la aplicación llamante a través de dialogRun. Según la documentación de la API de Gtk2Hs, dialogRun se bloquea en un bucle recursivo hasta que al diálogo o bien emite la señal de respuesta, o es destruido. El modo por defecto es modal, lo que indica que el usuario no puede acceder a ninguna otra ventana (de esta aplicación) mientras dialogRun esté esperando una respuesta.

Las barras de progreso se emplean para mostrar el estado de una operación en curso.

progressBarNew :: IO ProgressBar

A pesar de que sólo hay un tipo, hay dos modos diferentes de usar una barra de progreso. Si se conoce la cantidad de tarea realizada, la fracción (entre 0.0 y 1.0 incluido) se puede establecer con:

progressBarSetFraction :: ProgressBarClass self => self -> Double -> IO ()

Esto origina que la barra de progreso se "llene" con la cantidad indicada (entre 0.0 y 1.0). Para marcar el progreso, esta función se debe llamar cada cierto tiempo durante la operación.

Si no se conoce la parte de la operación completada, la barra puede moverse de atrás hacia adelante con:

progressBarPulse :: ProgressBarClass self => self -> IO ()

También esta función debe ser llamada repetidamente, para mostrar que la actividad sigue en marcha. Hay otras muchas funciones para controlar la presentación de una barra de progreso, como la orientación, texto adicional, etc.; Son muy sencillas.

La aplicación, sin embargo, no es trivial debido a que las barras de progreso se aplican normalmente con temporizadores u otras funciones para dar la ilusión de multitarea. Con Haskell concurrente puedes usar hilos(threads) y comunicación entre hilos.

En el siguiente ejemplo simulamos una actividad usando timeoutAdd, que ejecuta una función repetidamente con el intervalo especificado en milisegundos. La función se pasa a timeoutAdd y debe devolver un valor del tipo IO Bool. Cuando el valor es true, el temporizador se vuelve a activar, cuando es falso, se para. La prioridad de timeoutAdd es priorityDefault de tipo Priority.

timeoutAdd :: IO Bool -> Int -> IO HandlerId

En el ejemplo, definimos una función showPulse, que causa que la barra de progreso que compruebe y que siempre devuelva IO True. El paso, la cantidad que se moverá el indicador por la barra, se establece a 1.0 con progressBarSetPulseStep.

Progress bar pulsing

El ejemplo es un poco atípico en el uso del diálogo, ya que lo usamos para mostrar el progreso tras la pulsación del botón "aplicar". Para cerrar la aplicación, el diálogo debe ser destruido, destruyendo la ventana. Los botones de cerrar y cancelar no funcionan después de que "aplicar" haya sido seleccionada. Si han sido seleccionados antes de "aplicar", la aplicación se cierra. Esto se realiza comprobando la respuesta de dialogRun.

Si el widget de diálogo se destruye, se llama a mainQuit. Como mencioné arriba, un Dialog consta de una ventana y dos cajas. Se debe acceder a las cajas a través de funciones especiales, y la barra de progreso se empaqueta en la parte superior con dialogGetUpper. Los botones en un diálogo son visibles por defecto, pero los widgets de la parte superior, no. Un Dialog es una instancia de la WindowClass, y por tanto, podemos ponerle el título y/o definir su tamaño si queremos.

Una característica trivial a tener en cuenta: Un widget sólo puede ser visible si su padre es visible. Así, para mostrar la barra de progreso, usamos widgetShowAll en la caja vertical y no widgetShow en la barra de progreso.

import Graphics.UI.Gtk

main :: IO ()
main = do
  initGUI

  dia <- dialogNew
  set dia [windowTitle := "Time Flies"]
  dialogAddButton dia stockapply  Responseapply
  dialogAddButton dia stockCancel ResponseCancel
  dialogAddButton dia stockClose  ResponseClose

  pr <- progressBarNew
  progressBarSetPulseStep pr 1.0

  upbox <- dialogGetUpper dia
  boxPackStart upbox pr PackGrow 10
  widgetShowAll upbox

  answer <- dialogRun dia
  if answer == Responseapply 
     then do tmhandle <- timeoutAdd (showPulse pr) 500
             return ()
     else widgetDestroy dia

  onDestroy dia mainQuit
  mainGUI

showPulse :: ProgressBar -> IO Bool
showPulse b = do progressBarPulse b
                 return True