4.6 Entrada de texto y barras de estado

El widget Entry (Entrada de texto) permite que el texto sea tecleado y mostrado en una simple caja de línea de texto. Hay una gran cantidad de teclas que funcionan por defecto. Además, el usuario puede cambiar entre el modo de inserción y el modo de sobreescritura pulsando la tecla Insert.

Para crear un nuevo widget Entry, usa la siguiente función.

entryNew :: IO Entry

Para reemplazar o tomar el texto que está en el widget Entry:

entrySetText :: EntryClass self => self -> String -> IO ()

entryGetText :: EntryClass self => self -> IO String

Si no queremos que se pueda modificar el contenido de un widget Entry porque alguien escriba en él, podemos cambiar su estado editable. También podemos modificar su visibilidad (p. ej. para passwords), el número máximo de caracteres (0 si no hay máximo), si la entrada tiene un marco, o no, el número de caracteres para el que se va a dejar espacio, y algunos otros atributos. También es posible usar completado de texto. (mira EntryCompletion en la documentación de la API). Los atributos de Entry, (accesibles por get y set) son:

entryEditable :: EntryClass self => Attr self Bool  -- Por defecto True

entryVisibility :: EntryClass self => Attr self Bool  -- Por defecto True

entryMaxLength :: EntryClass self => Attr self Int -- 0 sin máximo, límite 66535

entryHasFrame :: EntryClass self => Attr self Bool -- Por defecto False

entryWidthChars :: EntryClass self => Attr self Int -- Por defecto -1, sin espacios

El tipo Entry es una instancia de EditableClass (Clase editable) y muchos de sus métodos y atributos están definidos allí. Algunos especialmente útiles son:

editableInsertText :: EditableClass self => self -> String -> Int -> IO Int

editableDeleteText :: EditableClass self -> Int -> Int -> IO ()

editableSelectRegion :: EditableClass self => self -> Int -> Int -> IO ()

editableDeleteSelection :: EditableClass self -> IO ()

donde los parámetros de tipo Int denotan las posiciones apropiadas de inicio y de finalización. El usuario también puede cortar, copiar y pegar a/desde el clipboard.

editableCutClipboard :: EditableClass self => self -> IO ()

editableCopyClipboard :: EditableClass self => self -> IO ()

editablePasteClipboard :: EditableClass self => self -> IO ()

Todas estas toman la posición actual del cursor. Puedes obtener y establecer dicha posición con:

editableGetPosition :: EditableClass self => self -> IO Int

editableSetPosition :: EditableClass self => self -> Int

El cursor se muestra antes del caracter con el índice indicado en el widget. El valor debe ser menor o igual al número de caracteres en el widget. El valor -1 indica que el cursor debe posicionarse después del último caracter en la entrada.

La clase Editable tiene unas señales que usan funciones de orden mayor (no las estudiamos aquí). El widget Entry tiene una señal que se envia después que el usuario pulsa la tecla Enter:

onEntryActivate :: EntryClass ec => ec -> IO () -> IO (ConnectId ec)

También hay señales que se envían cuando el texto se corta, copia o pega, o cuando el usuario cambia del modo insertar a sobreescribir.

Las barras de estado (Status Bars) son widgets simples usados para mostrar un mensaje de texto. Mantienen una pila de los mensajes que se les han enviado, de modo que al mostrar el mensaje actual, vuelven a mostrar los mensajes de texto anteriores. Por defecto el usuario puede modificar su tamaño.

Para poder permitir que diferentes partes de una aplicación usen la misma barra de estado para mostrar los mensajes, el widget de la barra de estado emplea ContextIds que sirven para identificar diferentes "usuarios". Se muestra el mensaje en la parte superior de la pila, independientemente de su contexto. Los mensajes se almacenan con un criterio ultimo en llegar primero en salir, y no ordenados por identificador de contexto. La barra de estado se crea con:

statusbarNew :: IO Statusbar

Para generar un nuevo ContextId utilizo la siguiente función, con un String usado como descripción textual del contexto:

statusbarGetContextId :: StatusbarClass self => self -> String -> IO ContextId

Aquí hay tres funciones que pueden operar en las barras de estado:

statusbarPush :: StatusbarClass self => self -> ContextId -> String -> IO MessageId

statusbarPop :: StatusbarClass self => self -> ContextId -> IO ()

statusbarRemove :: StatusbarClass self => self -> ContextId -> MessageId -> IO ()

La primera, statusbarPush, se emplea para añadir un mensaje nuevo a la barra de estado. Devuelve un MessageId, que puede ser pasado más tarde a statusbarRemove para eliminar el mensaje en el ContextId y MessageId de la pila de la barra de estado. La función statusbarPop elimina el mensaje más alto de la pila dentro del identificador de contexto aportado.

Las barras de estado, como las barras de progreso, se usan para mostrar mensajes al usuario sobre alguna operación en ejecución. En el ejemplo inferior, simulamos esta operación comprobando si el texto que envía el usuario (pulsando Enter) es el mismo que su inverso, y enviando el resultado a la pila. El usuario puede ver los resultados pulsando el botón de información, que muestra la pila de mensajes. La primera vez, la pila está vacía, así que el botón está sombreado, usando:

widgetSetSensitivity :: WidgetClass self => self -> Bool -> IO ()

Fíjate que la barra de estado no sería la primera opción aquí, ya que no se comprueba si la pila está vacía, pero el ejemplo muestra como se aplica. El manejador del cambio de tamaño de la ventana de la barra de estado no está muy claro, pero está ahí, abajo a la derecha.

Status bar example

import Graphics.UI.Gtk

main :: IO ()
main= do
  initGUI
  window <- windowNew
  set window [windowTitle := "Text Entry", containerBorderWidth := 10]

  vb <- vBoxNew False 0
  containerAdd window vb

  hb <- hBoxNew False 0
  boxPackStart vb hb PackNatural 0

  txtfield <- entryNew
  boxPackStart hb txtfield PackNatural 5
  button <- buttonNewFromStock stockInfo
  boxPackStart hb button PackNatural 0

  txtstack <- statusbarNew
  boxPackStart vb txtstack PackNatural 0
  id <- statusbarGetContextId txtstack "Line"

  widgetShowAll window
  widgetSetSensitivity button False

  onEntryActivate txtfield (saveText txtfield button txtstack id)
  onPressed button (statusbarPop txtstack id)
  onDestroy window mainQuit
  mainGUI

saveText :: Entry -> Button -> Statusbar -> ContextId -> IO ()
saveText fld b stk id = do
    txt <- entryGetText fld
    let mesg | txt == reverse txt = "\"" ++ txt ++ "\""  ++
                                    " is equal to its reverse"
             | otherwise =  "\"" ++ txt ++ "\""  ++
                            " is not equal to its reverse"
    widgetSetSensitivity b True
    msgid <- statusbarPush stk id mesg
    return ()