[Tutorial-Port Spanish Translation Chapter 4 (1-7) hthiel.char@zonnet.nl**20071215174442] addfile ./docs/tutorial/Tutorial_Port/es-chap4-1.xhtml hunk ./docs/tutorial/Tutorial_Port/es-chap4-1.xhtml 1 - + + + + + + Gtk2Hs Tutorial: The Button Widget + + + + + + + +

4.1 El Widget Button (Botón)

+ +

Los botones normales

+ +

+Ya casi hemos visto todo lo que hay que ver del widget botón. Es muy sencillo. +Sin embargo hay más de una manera de crear un botón. Puedes usar las funciones +buttonNewWithLabel o buttonNewWithMnemonic para crear +un botón con una etiqueta, usar buttonNewFromStock para crear un botón +que contenga una imagen y un texto de un "stock item" o usar buttonNew +para crear un botón vacío.Es cosa tuya colocarle una etiqueta o un pixmap en este nuevo botón. +Para hacer esto, crea una nueva caja y empaqueta los objetos dentro de ella usando +la función boxPackStart (o boxPackEnd +para empezar desde el final), que ya conoces, y después usa containerAdd para empaquetar +la caja en el botón. +

+ +

+Las funciones buttonNewWithMnemonic y buttonNewFromStock tienen un +string como primer argumento. Usa el subrayado para marcar un caracter como el "mnemonico", que +funcionará como un acelerador vía teclado. Pulsando Alt y esa tecla se activará dicho botón, sin +necesidad de pinchar con el ratón. En la segunda función, la cadena es un stockId, un identificador +de una lista de imágenes predefinidas con etiquetas. +

+ +

+Aquí tenemos un ejemplo de uso de buttonNew para crear un botón con una imagen y una etiqueta. +

+ +

Button with image

+ +
import Graphics.UI.Gtk
+
+main :: IO ()
+main = do
+  initGUI
+  window <- windowNew
+  set window [windowTitle := "Pix",
+              containerBorderWidth := 10]
+  button <- buttonNew
+  onClicked button (putStrLn "button clicked")
+  box    <- labelBox "info.xpm" "cool button"
+  containerAdd button box
+  containerAdd window button
+  widgetShowAll window
+  onDestroy window mainQuit
+  mainGUI
+
+labelBox :: FilePath -> String -> IO HBox
+labelBox fn txt = do
+  box   <- hBoxNew False 0
+  set box [containerBorderWidth := 2]
+  image <- imageNewFromFile fn
+  label <- labelNew (Just txt)
+  boxPackStart box image PackNatural 3
+  boxPackStart box label PackNatural 3
+  return box
+
+ +

+La función labelBox se puede usar para meter imágenes y etiquetas en +cualquier widget que sea simultáneamente un contenedor (container). La imagen procede de +un fichero, usando imageNewFromFile y la etiqueta viene de labelNew, +que utiliza un Maybe String como argumento. Nothing +indica que no hay etiqueta. +

+ +

+El widget Button tiene las siguientes señales básicas, que casi se autoexplican (si sabes inglés claro!): +

+ + + +

+Nota: Normalmente el dispositivo señalador es el ratón, por eso lo indico. Para cualquier +dispositivo señalador, el funcionamiento sería el mismo, así ratón = dispositivo señalador. +

+ + +

Botones Toggle (Alternativos)

+ +

+Los botones Toggle se derivan de los botones normales y son muy parecidos. Su única +diferencia estriba en que siempre alternan entre dos estados. La alternancia se +produce al ser pulsados. +

+ +

+Los botones Toggle son la base de de los botones check y radio, por tanto muchas +de las llamadas usadas por los botones toggle son heredadas por los botones check y +radio. +Ya lo recordaremos cuando lleguemos a ellos. +

+ +

+La creación de un boton toggle: +

+ +
toggleButtonNew :: IO ToggleButton
+
+toggleButtonNewWithLabel :: String -> IO Togglebutton
+
+toggleButtonNewWithMnemonic :: String -> IO ToggleButton
+
+ +

+Como puedes imaginar, funcionan exactamente igual que con los widget botón. +El primero crea un botón toggle vacío, y los dos últimos, un botón con un widget +de tipo etiqueta(label) ya empaquetado con él. La variante "mnemonica" analiza la etiqueta +buscando el caracter prefijado con un "_". +

+ +

+Para conocer el estado del botón toggle (pulsado o no pulsado), incluyendo los +botones radio y check, se emplea: +

+ +
toggleButtonGetActive :: ToggleButtonClass self => self -> IO Bool
+
+ +

+Devuelve True si el botón toggle está pulsado y False si no lo está. +

+ +

+Para forzar el estado de un botón toggle, o sus hijos, los botones radio y check, debes emplear: +

+ +
toggleButtonSetActive :: ToggleButtonClass self => self -> Bool -> IO ()
+
+ +

+La llamada anterior se puede usar para establecer el estado del botón toggle, y sus +hijos los botones radio y check. Hay que pasarle el botón creado como primer argumento +y True o False como segundo argumento, para especificar el estado an el que +queremos dejar el botón. El valor por defecto es "no pulsado" o False. +

+ +

+Fíjate que cuando usas la función toggleButtonSetActive, y se cambia el +estado, origina que se emitan las señales onClicked y +onToggled del botón. +

+ +

Botones Check

+ +

+Los botones Check heredan muchas propiedades y funciones de los botones toggle, bero +su apariencia es diferente. Más que ser botones con un texto dentro de ellos, son pequeños +cuadrados con el texto a su derecha. A menudo se usan para marcar las opciones +de una aplicación como activadas o desactivadas. +

+ +

+Las funciones de creación son las mismas que las de los botones normales. +

+ +
checkButtonNew :: IO CheckButton
+
+checkButtonNewWithLabel :: String -> IO Checkbutton
+
+checkButtonNewWithMnemonic :: String -> IO CheckButton
+
+ +

+La función checkButtonNewWithLabel crea un botón check con una etiqueta junto a él. +

+ +

+CheckButton es una instancia de ToggleButtonClass y +la señal onToggled se usa cuando un CheckButton se marca +o se desmarca, como en el botón toggle.

+ +

Botones Radio

+ +

+Los botones Radio son parecidos a los botones check excepto que están agrupados de modo que sólo uno +puede seleccionarse en cada momento. Esto es útil para los lugares del programa donde se debe seleccionar +una opción de entre varias posibles. Para crear un boton radio utilizo una de estas funciones: +

+ +
radioButtonNew :: IO RadioButton
+
+radioButtonNewWithLabel :: String -> IO RadioButton
+
+radioButtonNewWithMnemonic :: String -> IO RadioButton
+
+radioButtonNewFromWidget :: RadioButton -> IO RadioButton
+
+radioButtonNewWithLabelFromWidget :: RadioButton -> String -> IO RadioButton
+
+radioButtonNewWithMnemonicFromWidget :: RadioButton -> String -> IO RadioButton
+
+ +

+Como has visto, las últimas tres funciones tienen un argumento extra. Se usa para +unir los botones nuevos a los ya construidos en un grupo. +

+ +

+Es una buena opción seleccionar el botón que presenta el valor por defecto con: +

+ +
toggleButtonSetActive :: ToggleButtonClass self => self -> Bool -> IO ()
+
+ +

+Esta función se describió en los botones toggle, y funciona exactamente del mismo modo. +Una vez que los botones radio se agrupan, sólo uno del grupo puede estar activo en un momento +determinado. Si el usuario pulsa en uno de los botones, y después en otro, el primer botón +radio emitirá en primer lugar una señal onToggled (para indicar el cambio a inactivo), y +después el segundo emitirá su señal onToggled (para indicar su cambio a activo). +

+ +

+El siguiente ejemplo crea un grupo de botones radio con tres botones, y cuando +el usuario pulsa uno de los botones radio, el desactivado y el activado lo indicarán +a stdout, mediante putStrLn en la función setRadioState +definida al final. +

+ +

Radio buttons

+ +
import Graphics.UI.Gtk
+
+main :: IO ()
+main = do
+  initGUI
+  window  <- windowNew
+  set window [windowTitle := "Radio Button", containerBorderWidth := 5,
+              windowDefaultWidth := 200, windowDefaultHeight := 150]
+  box1    <- vBoxNew False 0
+  containerAdd window box1
+  box2    <- vBoxNew False 10
+  containerSetBorderWidth box2 10
+  boxPackStart box1 box2 PackNatural 0
+  button1 <- radioButtonNewWithLabel "button 1"
+  boxPackStart box2 button1 PackNatural 0
+  button2 <- radioButtonNewWithLabelFromWidget button1 "button 2"
+  boxPackStart box2 button2 PackNatural 0
+  button3 <- radioButtonNewWithLabelFromWidget button2 "button 3"
+  boxPackStart box2 button3 PackNatural 0
+  toggleButtonSetActive button2 True
+  onToggled button1 (setRadioState button1)
+  onToggled button2 (setRadioState button2)
+  onToggled button3 (setRadioState button3)
+  sep     <- hSeparatorNew
+  boxPackStart box1 sep PackNatural 0
+  box3    <- vBoxNew False 10
+  containerSetBorderWidth box3 10
+  boxPackStart box1 box3 PackNatural 0
+  closeb  <- buttonNewWithLabel "close"
+  boxPackStart box3 closeb PackNatural 0
+  onClicked closeb mainQuit
+  widgetShowAll window
+  onDestroy window mainQuit
+  mainGUI
+
+setRadioState :: RadioButton -> IO ()
+setRadioState b = do
+  state <- toggleButtonGetActive b
+  label <- get b buttonLabel
+  putStrLn ("State " ++ label ++ " now is " ++ (show state))
+
+ + + + + addfile ./docs/tutorial/Tutorial_Port/es-chap4-2.xhtml hunk ./docs/tutorial/Tutorial_Port/es-chap4-2.xhtml 1 - + + + + + + + + Tutorial de Gtk2Hs: Ajustes, escalado y rango + + + + + +

4.2 Ajustes, escalado y rango

+ +

+Gtk2Hs tiene varios widgets que pueden ser ajustados visualmente por el usuario +usando el ratón o el teclado, como los widgets rango, descritos en su sección. +También hay algunos widgets que muestran una porción de un área de datos mayor, +como el widget text y el widget viewport. Esta porción también puede ser ajustada +por el usuario. +

+ +

+Una aplicación necesita ser capaz de reaccionar a los cambios que hace el usuario +en el rango. Un modo de hacerlo podría ser haciendo que cada widget emitiera +su propio tipo de señal cuando cambiase su ajuste. Pero puede que quisieras enlazar +los ajustes de varios widgets juntos, de modo que al ajustar uno se ajustasen los otros. +El ejemplo más claro de esto es la conexión de un scrollbar (barra de deslizamiento) a un +panning viewport (ventana de mostrar imágenes con una subpantalla) o a un área de texto con scroll. +

+ +

+El objeto Adjustment (ajuste) se puede usar para almacenar los parámetros y valores de +configuración de los widgets de rango, como los scrollbars y controles de escalado. +Como Adjustment se deriva de GObject y +Object, los ajustes pueden emitir señales, que pueden ser usadas no sólo para +que tu programa reaccione a las entradas del usuario en los widgets ajustables, sino +también para propagar los valores de ajuste de un modo transparente entre widgets ajustables. +

+ +

+Muchos de los widgets que usan objetos Adjustment, como ScrolledWindow, pueden crear +sus propios objetos Adjustment, pero tu mismo los puedes crear con: +

+ +
adjustmentNew :: Double        -- valor            - El valor inicial del rango
+              -> Double        -- mínimo           - Valor mínimo del rango
+              -> Double        -- máximo           - Valor máximo del rango
+              -> Double        -- incrementoPaso   - El menor de dos posibles incrementos
+              -> Double        -- incrementoPágina - El mayor de dos posibles incrementos
+              -> Double        -- tamañoPágina     - El tamaño del área visible
+              -> IO Adjustment
+
+ +

+La función de creación necesita un valor para cada información contenida en el objeto: +valor Es el valor inicial y debe estar contenido entre el maximo y el mínimo. +máximo y mínimo fijan el margen del deslizador. Pulsando en las flechas +se incrementa el valor en incrementPaso. Pulsando en el deslizador (slider) avanza un +incrementoPágina. El tamañoPágina se necesita para determinar si el final +del deslizador se mantiene en el rango. Puedes conseguir y establecer todos los parámetros de un +Adjustment por métodos específicos o usando las funciones generales set y get +en los atributos de Adjustment. +

+ +
onValueChanged :: Adjustment -> IO () -> IO (ConnectId Adjustment)
+
+ +

+es la señal emitida cuando el "valor" de un Adjustment cambia, y: +

+ +
onAdjChanged :: Adjustment -> IO () -> IO (ConnectId Adjustment)
+
+ +

+es la señal emitida cuando cambian uno o más de los otros atributos (diferentes de "valor") de un Adjustment. +

+ +

Widgets de escala y rango

+ +

+Los widgets de escalado se usan para permitir al usuario la selección visual +y la manipulación del valor dentro de un rango especificado usando un deslizador. +Podrías querer usar un widget escalable, por ejemplo, para ajustar el nivel de +ampliación en una previsualización reducida de una imagen, o para controlar el +brillo de un color o para especificar el número de minutos de inactividad antes de +que un salvapantallas se active.

+ +

+Las siguientes funciones crean widgets de escalado vertical y horizontal respectivamente: +

+ +
vScaleNew :: Adjustment -> IO VScale
+
+hScaleNew :: Adjustment -> IO Hscale
+
+ +

+También hay dos constructores, que no utilizan el Adjustment: +

+ +
vScaleNewWithRange :: Double ->. Double -> Double -> IO VScale
+
+hScaleNewWithRange :: Double ->. Double -> Double -> IO Hscale
+
+ +

+Los parámetros de tipo Double se refieren a los valores máximo, mínimo y el +paso. El incremento del paso (preferiblemente en potencias de 10) es el valor en que se +mueve la escala cuando se usan las teclas de cursor (flechas). +

+ +

+Las escalas horizontal y vertical son instancias de ScaleClass y +su comportamiento común está definido en el módulo +Graphics.UI.Gtk.Abstract.Scale. +

+ +

+Los widgets de escalado pueden mostrar su valor actual como un número junto al canal. +(El área donde se mueve el deslizador) +El comportamiento por defecto es mostrar el valor, pero puedes modificarlo con la función: +

+ +
scaleSetDrawValue :: ScaleClass self => self -> Bool -> IO ()
+
+ +

+El valor mostrado por un widget de escalado se redondea en un punto decimal +por defecto, igual que el ajuste del campo valor. Esto se puede cambiar con: +

+ +
scaleSetDigits :: ScaleClass self => self -> Int -> IO ()
+
+ +

+Finalmente, el valor puede ser dibujado en diferentes posiciones relativas al canal: +

+ +
scaleSetValuePos :: ScaleClass self => self -> PositionType -> IO ()
+
+ +

+La PositionType (tipo de posición) se define como: +

+ +
data PositionType = PosLeft | PosRight | PosTop | PosBottom
+
+ +

+Scale hereda diferentes métodos de su clase base que es: Range. +

+ +

Estableciendo la política de actualizaciones

+ +

+La política de actualizaciones de un widget rango define los puntos a los que, durante +la interacción con el usuario, cambiará el campo valor de su +Adjustment y emitirá la señal onRangeValueChanged +de su Adjustment. La política de actualizaciones esta definida por el +UpdateType, que tiene tres constructores: +

+ +
+
UpdateContinuous (Actualización continua )
+
+ Es el valor por defecto. La señal onRangeValueChanged se emite continuamente, + p.ej., cuando el deslizador se mueve un valor mínimo. +
+ +
UpdateDiscontinuous (Actualización discontinua)
+
+ La señal onRangeValueChanged sólo se emite cuando el deslizador se ha detenido + y el usuario ha levantado el botón del ratón. +
+ +
UpdateDelayed (Actualización retrasada)
+
+ La señal onRangeValueChanged se emite cuando el usuario levanta el botón del + ratón o si el deslizador se para de mover durante un corto periodo de tiempo. +
+
+ +

+La política de actualizaciones de un widget range se puede establecer por: +

+ +
rangeSetUpdatePolicy :: RangeClass self => self -> UpdateType -> IO ()
+
+ +

Obteniendo y estableciendo los ajustes

+ +

+La obtención y el ajuste de un widget rango se hace sobre la marcha con: +

+ +
rangeGetAdjustment :: RangeClass self => self -> IO Adjustment
+
+rangeSetAdjustment :: RangeClass self => self -> Adjustment -> IO ()
+
+ +

+rangeSetAdjustment no hace nada si le pasas el objeto de Adjustment que ya está usando, +independientemente de que alguno de los valores de sus atributos haya cambiado. +Si le pasas un nuevo código de Adjustment, desconectará el antiguo, si existía +(posiblemente detruyéndolo), conectará las señales apropiadas al nuevo, y llamará a la función +privada gtk_range_adjustment_changed(), que (o al menos se supone que...) recalculará +el tamaño y/o posición del deslizador y lo dibujará si fuera necesario. +Como se ha mencionado en la sección de ajustes, si quieres volver a usar el mismo Adjustment, +cuando modificas sus valores directamente, deberías emitir la señal changed (cambiado) sobre él. +

+ +

Interacción con teclado y ratón

+ +

+Todos los widgets de rango de Gtk2Hs reaccionan a las pulsaciones de ratón más +o menos de la misma manera. Pulsando del botón 1 del ratón en el canal originará +que el ajuste de incrementoPaso se añada o sustraiga de su valor, +y el deslizador se mueva de acuerdo a ello. Pulsando el botón 2 del ratón en el canal +moverá el deslizador al punto en que se pulsó el botón. Pulsando el tercer botón en el +canal o en las flechas del scroll causará que el valor del Adjustment +cambie en el valor de incrementoPaso. +

+ +

+Nota: Esto no funciona en Linux Fedora 6 con la configuración estándar del ratón. +

+ +

+No se puede hacer que las barras de scroll tengan el foco, así que no disponen de atajos de teclado. +Para los otros widgets de rango, (que sólo están activos cuando el widget tiene el foco) no +diferencian entre widgets de rango vertical u horizontal. +

+ +

+Todos los widgets de rango se pueden usar con las flechas de izquierda, derecha, arriba y abajo, +así como con las teclas AvPág y RePág. Las flechas mueven el +deslizador arriba y abajo en incrementoPaso, mientras AvPág y +RePág lo mueven en incrementoPágina. Inicio y Fin +lo mueven al principio y al final del canal. +

+ + +

Ejemplo

+ +

+Este ejemplo crea una ventana con tres widgets de rango conectados al mismo objeto Adjustment, +y un par de controles para ajustar algunos de los parámetros mencionados antes, de modo +que puedas ver como afecta al modo en que dichos widgets trabajan. +

+ +

Range widgets example

+ +

+Las tres escalas se sitúan de modo que la barra vertical está junto a las dos +horizontales, una sobre la otra. Así que necesitamos una caja horizontal para la escala vertical +y una caja vertical junto a ella para las escalas horizontales. Las escalas y las cajas +deben empaquetarse con PackGrow de modo que las escalas actualicen su tamaño con el de +la caja principal, que es una caja vertical en la ventana. +

+ +

+Todas las escalas se construyen con el mismo Adjustment, estableciendo el valor inicial en 0.0, +el valor mínimo en 0.0, el máximo en 101.0, el incremento del paso en 0.1, el incremento de +página en 1.0 y el tamaño de la página en 1.0. +

+ +
  adj1 <- adjustmentNew 0.0 0.0 101.0 0.1 1.0 1.0
+
+ +

+El usuario puede controlar cuando se muestran los valores de escalado con un +checkButton. Esto se empaqueta en la caja principal y se establece +para estar activo inicialmente. Un botón check es un botón toggle, así que cuando +el usuario lo marca o lo desmarca, se envía la señal onToggled. +Esto origina que la función toggleDisplay +se evalúe. La función toggleDisplay queda definida así: +

+ +
toggleDisplay :: ScaleClass self => CheckButton -> [self] -> IO ()
+toggleDisplay b scls = sequence_ (map change scls) where
+                         change sc = do st <- toggleButtonGetActive b
+                                        scaleSetDrawValue sc st
+
+ +

+La función tiene un parámetro tipo checkButton, y una lista de instancias +de ScaleClass. Sin embargo, una lista sólo puede contener valores del mismo tipo, +y vScale y hScale son tipos diferentes. Por lo tanto, usamos la función +en listas de escalas horizontales o verticales, pero si la lista contuviera los dos tipos se originaría un +error de tipado. +

+ +

+El usuario puede elegir el positionType mediante un widget del que todavía no hemos hablado, +un ComboBox. Esto permite una selección de elecciones como se muestra más abajo. El primero en +establecerse como activo se determina por un índice, 0 aquí (el primero). +

+ +
makeOpt1 :: IO ComboBox
+makeOpt1 = do
+  cb <- comboBoxNewText
+  comboBoxAppendText cb "TOP"
+  comboBoxAppendText cb "BOTTOM"
+  comboBoxAppendText cb "LEFT"
+  comboBoxAppendText cb "RIGHT"
+  comboBoxSetActive cb 0
+  return cb
+
+ +

+Un segundo comboBox permite que el usuario seleccione la política de actualizaciones, +alguno de los tres constructores de UpdateType. +

+ +
makeOpt2 :: IO ComboBox
+makeOpt2 = do
+  cb <- comboBoxNewText
+  comboBoxAppendText cb "Continuous"
+  comboBoxAppendText cb "Discontinuous"
+  comboBoxAppendText cb "Delayed"
+  comboBoxSetActive cb 0
+  return cb
+
+ +

+Las cajas combo (Combo box) por si mismas sólo muestran texto. Para seleccionar +la posición, definimos: +

+ +
setScalePos :: ScaleClass self => ComboBox -> self -> IO ()
+setScalePos cb sc = do
+    ntxt <- comboBoxGetActiveText cb
+    let pos = case ntxt of
+                (Just "TOP")    -> PosTop
+                (Just "BOTTOM") -> PosBottom
+                (Just "LEFT")   -> PosLeft
+                (Just "RIGHT")  -> PosRight
+                Nothing         -> error "setScalePos: no position set"
+    scaleSetValuePos sc pos
+
+setUpdatePol :: RangeClass self => ComboBox -> self -> IO ()
+setUpdatePol cb sc = do
+    ntxt <- comboBoxGetActiveText cb
+    let pol = case ntxt of
+                (Just "Continuous")    -> UpdateContinuous
+                (Just "Discontinuous") -> UpdateDiscontinuous
+                (Just "Delayed")       -> UpdateDelayed
+                Nothing                -> error "setUpdatePol: no policy set"
+    rangeSetUpdatePolicy sc pol
+
+ +

+Aquí no hemos usado listas para gestionar las escalas verticales y horizontales, así que +accedemos por separado a cada escala horizontal. +

+ +

+El número de precisión mostrado por las tres escalas debe ser manejado con otra escala, +para la que usamos un nuevo objeto Adjustment. La precisión máxima es 5 y cada incremento es 1. +La precisión de la propia escala de control se establece a 1. +

+ +
  adj2 <- adjustmentNew 1.0 0.0 5.0 1.0 1.0 0.0
+
+ +

+Cuando cambia el Adjustment de control, se emite la señal onValueChanged y se +evalúa la función setDigits. +

+ +
setDigits :: ScaleClass self => self -> Adjustment -> IO ()
+setDigits sc adj = do val <- get adj adjustmentValue
+                      set sc [scaleDigits := (round val)]
+
+ +

+Aquí usamos las funciones generales set y get sobre los +atributos; podríamos haber usado funciones específicas también. fíjate en que el +valor de tipo Double del valor de ajuste, se debe redondear a un valor de +tipo Integral. +

+ +

+Usamos otra escala horizontal para gestionar el tamaño de página de las tres +escalas del ejemplo. Cuando lo ponemos a 0.0, las escalas pueden alcanzar su +máximo inicial de 100.0 y cuando lo ponemos a 100.0 las escalas quedan fijas +en su menor valor. Esto implica el ajuste del +adjustment por una señal de onValueChanged desde un tercer +adjustment mediante este trozo de código: +

+ +
  onValueChanged adj3 $ do val <- adjustmentGetValue adj3
+                           adjustmentSetPageSize adj1 val
+
+ +

+La función main es: +

+ +
import Graphics.UI.Gtk
+
+main :: IO ()
+main = do
+  initGUI
+  window  <- windowNew
+  set window [windowTitle := "range controls",
+              windowDefaultWidth := 250]
+  mainbox <- vBoxNew False 10
+  containerAdd window mainbox
+  containerSetBorderWidth mainbox 10
+
+  box1 <- hBoxNew False 0
+  boxPackStart mainbox box1 PackGrow 0
+  adj1 <- adjustmentNew 0.0 0.0 101.0 0.1 1.0 1.0
+  vsc  <- vScaleNew adj1
+  boxPackStart box1 vsc PackGrow 0
+
+  box2 <- vBoxNew False 0
+  boxPackStart box1 box2 PackGrow 0
+
+  hsc1 <- hScaleNew adj1
+  boxPackStart box2 hsc1 PackGrow 0
+  hsc2 <- hScaleNew adj1
+  boxPackStart box2 hsc2 PackGrow 0
+
+  chb <- checkButtonNewWithLabel "Display Value on Scale Widgets"
+  boxPackStart mainbox chb PackNatural 10
+  toggleButtonSetActive chb True
+
+  box3   <- hBoxNew False 10
+  boxPackStart mainbox box3 PackNatural 0
+  label1 <- labelNew (Just "Scale Value Position:")
+  boxPackStart box3 label1 PackNatural 0
+  opt1   <- makeOpt1
+  boxPackStart box3 opt1 PackNatural 0
+
+  box4   <- hBoxNew False 10
+  boxPackStart mainbox box4 PackNatural 0
+  label2 <- labelNew (Just "Scale Update Policy:")
+  boxPackStart box4 label2 PackNatural 0
+  opt2   <- makeOpt2
+  boxPackStart box4 opt2 PackNatural 0
+
+  adj2 <- adjustmentNew 1.0 0.0 5.0 1.0 1.0 0.0
+
+  box5   <- hBoxNew False 0
+  containerSetBorderWidth box5 10
+  boxPackStart mainbox box5 PackGrow 0
+  label3 <- labelNew (Just "Scale Digits:")
+  boxPackStart box5 label3 PackNatural 10
+  dsc    <- hScaleNew adj2
+  boxPackStart box5 dsc PackGrow 0
+  scaleSetDigits dsc 0
+
+  adj3 <- adjustmentNew 1.0 1.0 101.0 1.0 1.0 0.0
+
+  box6   <- hBoxNew False 0
+  containerSetBorderWidth box6 10
+  boxPackStart mainbox box6 PackGrow 0
+  label4 <- labelNew (Just "Scrollbar Page Size:")
+  boxPackStart box6 label4 PackNatural 10
+  psc    <- hScaleNew adj3
+  boxPackStart box6 psc PackGrow 0
+  scaleSetDigits psc 0
+
+  onToggled chb $ do toggleDisplay chb [hsc1,hsc2]
+                     toggleDisplay chb [vsc]
+
+  onChanged opt1 $ do setScalePos opt1 hsc1
+                      setScalePos opt1 hsc2
+                      setScalePos opt1 vsc
+
+  onChanged opt2 $ do setUpdatePol opt2 hsc1
+                      setUpdatePol opt2 hsc2
+                      setUpdatePol opt2 vsc
+
+  onValueChanged adj2 $ do setDigits hsc1 adj2
+                           setDigits hsc2 adj2
+                           setDigits vsc  adj2
+
+  onValueChanged adj3 $ do val <- adjustmentGetValue adj3
+                           adjustmentSetPageSize adj1 val
+
+  widgetShowAll window
+  onDestroy window mainQuit
+  mainGUI
+
+ +

+Las funciones no estándar usadas en el listado ya han sido listadas antes. +

+ + + + + addfile ./docs/tutorial/Tutorial_Port/es-chap4-3.xhtml hunk ./docs/tutorial/Tutorial_Port/es-chap4-3.xhtml 1 - + + + + + + + + Tutorial de Gtk2Hs: Etiquetas + + + + + +

4.3 Etiquetas

+ +

+Las etiquetas se usan mucho en Gtk2Hs, y son relativamente sencillas. Las etiquetas +no emiten señales, ya que no tienen una X Window asociada. Si necesitas capturar +una señal o apilarlas con otros widgets, debes colocarla dentro de un widget EventBox (Caja de eventos), +Los EventBox te permiten capturar señales de widgets que no tienen su +propia ventana o botón. +

+ +

+Para crear una nueva etiqueta, puedes usar: +

+ +
labelNew :: Maybe String -> IO Label
+
+labelNewWithMnemonic :: String -> IO Label
+
+ +

+Con la segunda función, si algún caracter de la cadena está precedido de un +guión bajo (o subrayado), se subraya al representarlo. Si necesitas un carácter +guión bajo en una etiqueta, debes usar "__" (dos subrayados). El primer caracter subrayado +que aparece representa un atajo de teclado que denominamos mnemónico. Cuando se pulsa esa +tecla, el widget disponible que contiene esa etiqueta (un botón, por ejemplo) se activa. +El widget al que queremos asociar un nmemónico se puede establecer con +labelSetMnemonicWidget. +

+ +

+Para cambiar el texto de la etiqueta después de haber sido creada, o para +conseguir el texto de la misma, puedes usar las funciones: +

+ +
labelSetText :: LabelClass self => self -> String -> IO ()
+
+labelGetLabel :: LabelClass self => self -> IO String
+
+ +

+o, por supuesto, las funciones genéricas set o get. El +espacio necesario para la nueva cadena (string) sería ajustado automáticamente si +fuera preciso. Se pueden producir etiquetas multilínea, colocando saltos de línea en +la cadena de la etiqueta. La justificación del texto en las etiquetas multilínea se realiza +con la función: +

+ +
labelSetJustify :: LabelClass self => self -> Justification -> IO ()
+
+ +

+donde el tipo Justification puede usar alguno de estos cuatro constructores: +

+ + + +

+El widget etiqueta es también capaz de fijar los saltos de línea en el texto automáticamente. +Esto se puede activar usando: +

+ +
labelSetLineWrap :: LabelClass self => self -> Bool -> IO ()
+
+ +

+Si quieres que la etiqueta esté subrayada, puedes establecer un patrón para la etiqueta: +

+ +
labelSetPattern :: LabelClass self => self -> [Int] -> IO ()
+
+ +

+La lista de Ints (enteros) marca las partes del texto de la etiqueta que se subrayarán +alternando con los caracteres que no se subrayarán. Por ejemplo, [3, 1, 3] significa +que se subrayarán los tres primeros caracteres, el siguiente, no y los tres siguientes, sí. +

+ +

+Puedes hacer, también, que se pueda seleccionar el texto de la etiqueta, de modo +que el usuario pueda copiarlo y pegarlo, además de usar algunas opciones de formateo. +

+ +

+Abajo hay un ejemplo que ilustra algunas de estas funciones. Usa el widget Frame +para demostrar mejor los estilos de la etiqueta. Un Frame no es más que un ornamento, +igual que un HSeparator y un VSeparator pero en este caso rodea al widget y +es una instancia de Container. Por tanto, el widget que encuadra debe ser añadido +con containerAdd. Un Frame puede tener una etiqueta para transmitir +información sobre su contenido. +

+ +

Label examples

+ +

+Como todas las etiquetas del ejemplo tienen un Frame, se define +una función myLabelWithFrameNew, que devuelve una tupla. Gtk2Hs es 100% Haskell, +por lo que puedes usar todos los tipos de datos y característas de Haskell. +Las justificaciones son obvias pero sólo se refieren a las líneas de dentro de la etiqueta. +Así que, para justificar a la derecha label2, necesitas miscSetAlignment tal y +como se muestra más abajo. Los últimos dos widgets en la caja horizontal de la izquierda se +empaquetan con boxPackEnd en vez del usual boxPackStart. La +etiqueta botón demuestra el uso de un mnemónico como atajo. Pulsar Alt-C +en el teclado provoca el mismo efecto que pulsar el botón con el ratón. +

+ +

+Nota: Cuando se ha testeado en Fedora 6, pulsar Enter +o la barra espaciadora tiene el mismo efecto. Fíjate además en el efecto del carácter +"y" en la colocación del subrayado. +

+ +
import Graphics.UI.Gtk
+
+main:: IO ()
+main = do
+  initGUI
+  window  <- windowNew
+  set window [windowTitle := "Labels", containerBorderWidth := 10]
+  mainbox <- vBoxNew False 10
+  containerAdd window mainbox
+  hbox    <- hBoxNew True 5
+  boxPackStart mainbox hbox PackNatural 0
+  vbox1   <- vBoxNew False 10
+  vbox2   <- vBoxNew False 0
+  boxPackStart hbox vbox1 PackNatural 0
+  boxPackStart hbox vbox2 PackNatural 0
+
+  (label1,frame1) <- myLabelWithFrameNew
+  boxPackStart vbox1 frame1 PackNatural 0
+  labelSetText label1 "Penny Harter"
+
+  (label2,frame2) <- myLabelWithFrameNew
+  boxPackStart vbox1 frame2 PackNatural 0
+  labelSetText label2 "broken bowl\nthe pieces\nstill rocking"
+  miscSetAlignment label2 0.0 0.0
+  hsep1           <- hSeparatorNew
+  boxPackStart vbox1 hsep1 PackNatural 10
+
+  (label3,frame3) <- myLabelWithFrameNew
+  boxPackStart vbox1 frame3 PackNatural 0
+  labelSetText label3 "Gary Snyder"
+
+  (label4,frame4) <- myLabelWithFrameNew
+  boxPackStart vbox1 frame4 PackNatural 0
+  labelSetText label4 "After weeks of watching the roof leak\nI fixed it tonight\nby moving a single board"
+  labelSetJustify label4 JustifyCenter
+
+  (label5,frame5) <- myLabelWithFrameNew
+  boxPackStart vbox2 frame5 PackNatural 0
+  labelSetText label5 "Kobayashi Issa"
+
+  (label7,frame7) <- myLabelWithFrameNew
+  boxPackEnd vbox2 frame7 PackNatural 0
+  labelSetText label7 "only one guy and\nonly one fly trying to\nmake the guest room do"
+  labelSetJustify label7 JustifyRight
+
+  (label6,frame6) <- myLabelWithFrameNew
+  boxPackEnd vbox2 frame6 PackNatural 10
+  labelSetText label6 "One Guy"
+  frameSetLabel frame6 "Title:"
+  labelSetPattern label6 [3, 1, 3]
+
+  button      <- buttonNew
+  boxPackEnd mainbox button PackNatural 20
+  buttonlabel <- labelNewWithMnemonic "Haiku _Clicked"
+  containerAdd button buttonlabel
+
+  widgetShowAll window
+  onClicked button (putStrLn "button clicked...")
+  onDestroy window mainQuit
+  mainGUI
+
+
+myLabelWithFrameNew :: IO (Label,Frame)
+myLabelWithFrameNew = do
+  label <- labelNew Nothing
+  frame <- frameNew
+  containerAdd frame label
+  frameSetShadowType frame ShadowOut
+  return (label, frame)
+
+
+-- Haikus quoted from X.J. Kennedy, Dana Gioia, Introduction to Poetry, Longman, 1997
+
+ + + + + addfile ./docs/tutorial/Tutorial_Port/es-chap4-4.xhtml hunk ./docs/tutorial/Tutorial_Port/es-chap4-4.xhtml 1 - + + + + + Tutorial de Gtk2Hs: Flechas y Tooltips + + + + + +

4.4 Flechas y Tooltips

+ +

+El widget Arrow (flecha) dibuja una cabeza de flecha, apuntando en una dirección y con un estilo +seleccionable. Igual que el widget etiqueta, no emite señales. +

+ +

+Sólo hay dos funciones para manipular un widget Arrow: +

+ +
arrowNew :: ArrowType -> ShadowType -> IO Arrow
+
+arrowSet :: ArrowClass self => self -> ArrowType -> ShadowType -> IO ()
+
+ +

+El ArrowType tiene cinco constructores: +

+ + + +

+El ShadowType (tipo de sombra) también tiene cinco constructores: +

+ + + +

+Los Tooltips son esas pequeñas frases que surgen cuando dejas el puntero sobre +un botón u otro widget durante unos segundos. +

+ +

+Los widgets que no reciben eventos (los que no tienen su propia ventana) no funcionan +con los tooltips. +

+ +

+Esta primera llamada creará un tooltip nuevo. Sólo necesitas llamarla una vez para crear +un conjunto de tooltips. +

+ +
tooltipsNew :: IO Tooltips
+
+ +

+Después, para cada widget, usa: +

+ +
tooltipsSetTip :: (TooltipsClass self, WidgetClass widget)
+  => self -> widget -> String -> String -> IO ()
+
+ +

+El primer argumento es el tooltip que ya has creado, seguido por el widget +para el que quieres el tooltip y el texto que quieres que aparezca. El +último argumento es una cadena de texto que puede usarse como su identificador. +

+ +

+Puedes activar o desactivar los mensajes asociados a un Tooltips mediante: +

+ +
tooltipsEnable :: TooltipsClass self => self -> IO ()
+tooltipsDisable :: TooltipsClass self => self -> IO ()
+
+ +

+Aquí tienes un ejemplo que ilustra el uso de flechas y tooltips. +

+ +

Arrow and tooltip example

+ +

+La ventana superior ha sido cambiada de tamaño, para mostrar como el empaquetado de +una tabla preserva el espacio de los botones con sus flechas. +

+ +

+Nota: Los tooltips no funcionan con GHCi (en mi máquina) pero +sí lo hacen cuando se compila. Por supuesto, no los puedes ver en la imagen. +

+ +
import Graphics.UI.Gtk
+
+main :: IO ()
+main = do
+  initGUI
+  window <- windowNew
+  set window [windowTitle := "Arrow Tips",
+              windowDefaultWidth := 200,
+              windowDefaultHeight := 200, containerBorderWidth := 20]
+
+  table <- tableNew 5 5 True
+  containerAdd window table
+
+  button1 <- buttonNew
+  button2 <- buttonNew
+  button3 <- buttonNew
+  button4 <- buttonNew
+
+  tableAttachDefaults table button1 0 1 2 3
+  tableAttachDefaults table button2 2 3 0 1
+  tableAttachDefaults table button3 4 5 2 3
+  tableAttachDefaults table button4 2 3 4 5
+
+  tlt <- tooltipsNew
+
+  arrow1 <- arrowNew ArrowLeft ShadowEtchedIn
+  containerAdd button1 arrow1
+  tooltipsSetTip tlt button1 "West" "T1"
+
+  arrow2 <- arrowNew ArrowUp ShadowEtchedOut
+  containerAdd button2 arrow2
+  tooltipsSetTip tlt button2 "North" "T2"
+
+  arrow3 <- arrowNew ArrowRight ShadowEtchedIn
+  containerAdd button3 arrow3
+  tooltipsSetTip tlt button3 "East" "T3"
+
+  arrow4 <- arrowNew ArrowDown ShadowEtchedOut
+  containerAdd button4 arrow4
+  tooltipsSetTip tlt button4 "South" "T4"
+
+  tooltipsEnable tlt
+  widgetShowAll window
+  onDestroy window mainQuit
+  mainGUI
+
+ + + + + addfile ./docs/tutorial/Tutorial_Port/es-chap4-5.xhtml hunk ./docs/tutorial/Tutorial_Port/es-chap4-5.xhtml 1 - + + + + + + Tutorial de Gtk2hs: Diálogos, Stock Items y barras de Progreso + + + + + +

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
+
+ + + + + addfile ./docs/tutorial/Tutorial_Port/es-chap4-6.xhtml hunk ./docs/tutorial/Tutorial_Port/es-chap4-6.xhtml 1 - + + + + + Tutorial de Gtk2Hs: Entrada de texto y barras de estado + + + + + +

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 ()
+
+ + + + + addfile ./docs/tutorial/Tutorial_Port/es-chap4-7.xhtml hunk ./docs/tutorial/Tutorial_Port/es-chap4-7.xhtml 1 - + + + + + + Tutorial de Gtk2Hs: Botones aumentar/disminuir + + + + + +

4.7 Botones aumentar/disminuir

+ +

+El widget SpinButton se usa normalmente para permitir al usuario +seleccionar un valor dentro de un rango de valores numéricos. Consta de una caja +de entrada de texto con dos botones flecha (arriba y abajo) enganchados en el +lateral. Seleccionando alguna de las flechas el valor se modifica subiendo +o bajando dentro del rango especificado. +La caja de entrada de texto también puede ser editada directamente para introducir +un valor. +SpinButton es una instancia de EditableClass, por lo que los +atributos y funciones definidas allí también están disponibles. +

+ +

+Los botones aumentar/disminuir permiten definir un valor con cero o 'n' decimales. El incremento, (el paso), +se define por el programador. Si mantenemos pulsado uno de los botones se puede +originar una aceleración en el cambio del valor, en función del tiempo en que esté +pulsado.

+ +

+SpinButton usa un objeto de Adjustment para almacenar +la información sobre el rango de valores que puede tomar. Recuerda que el widget +Adjustment se crea con la siguiente función: +

+ +
adjustmentNew :: Double        -- valor            - El valor inicial del rango
+              -> Double        -- mínimo           - Valor mínimo del rango
+              -> Double        -- máximo           - Valor máximo del rango
+              -> Double        -- incrementoPaso   - El menor de dos posibles incrementos
+              -> Double        -- incrementoPágina - El mayor de dos posibles incrementos
+              -> Double        -- tamañoPágina     - El tamaño del área visible
+              -> IO Adjustment
+
+ +

+Los atributos del objeto Adjustment asociado al SpinButton +tienen el siguiente uso: +

+ + + +

+Además, el pusador 3 del ratón se puede usar para ir directamente a los valores +máximo y mínimo al seleccionar alguno de los botones. Ten en cuenta que esto puede +depender de la configuración del ratón en tu sistema. +

+ +

+Vamos a ver como podemos crear un botón aumentar/disminuir: +

+ +
spinButtonNew :: Adjustment -> Double -> Int -> IO SpinButton
+
+ +

+El segundo argumento (climbRate) (tasa de escalado) es un valor entre 0.0 y 1.0 +e indica la velocidad de cambio del botón aumentar/disminuir cuando se pulsa una tecla. El tercer argumento +indica el número de posiciones decimales con que será mostrado el valor. +

+ +

+También hay un constructor que permite crear un botón aumentar/disminuir sin necesidad de crear un adjustment. +

+ +
spinButtonNewWithRange :: Double -> Double -> Double -> IO SpinButton
+
+ +

+Los tres argumentos, todos de tipo Double, corresponden al valor +mínimo posible, al máximo, y al incremento añadido o sustraído al pulsar +los botones del widget. +

+ +

+Una vez creado, el SpinButton se puede reconfigurar mediante la siguiente función: +

+ +
spinButtonConfigure :: SpinButtonClass self => self -. Adjustment -> Double -> Int
+
+ +

+El primer argumento indica el widget SpinButton que debe ser +reconfigurado. los otros argumentos son, el climbRate y el número +de decimales a mostrar. +

+ +

+Los atributos del SpinButton que pueden ser modificados o consultados +con las funciones genéricas get y set son: +

+ +
spinButtonAdjustment :: SpinButtonClass self => Attr self Adjustment
+spinButtonClimbRate  :: SpinButtonClass self => Attr self Double
+spinButtonDigits     :: SpinButtonClass self => Attr self Int
+
+spinButtonSnapToTicks :: SpinButtonClass self => Attr self Bool
+spinButtonNumeric     :: SpinButtonClass self => Attr self Bool
+spinButtonWrap        :: SpinButtonClass self => Attr self Bool
+
+spinButtonValue :: SpinButtonClass self => Attr self Double
+
+ +

+Los primeros tres valores ya los hemos analizado. El atributo +spinButtonSnapToTicks determina si los valores erróneos +se cambian automáticamente a valores válidos del botón aumentar/disminuir (por defecto es +False). El attributo spinButtonNumeric determina si +los valores no-numéricos deben ser ignorados (por defecto es False), y +spinButtonWrap se usa si un botón aumentar/disminuir debe funcionar de una manera +circular. (por defecto es False). +

+ +

+El atributo spinButtonValue se usa para leer el valor actual o establecer un +nuevo valor (por defecto es 0). +

+ +

+También puedes usar: +

+ +
spinButtonSpin :: SpinButtonClass self => self -> SpinType -> Double -> IO ()
+
+ +

+donde SpinType determina el tipo de cambio y +Double (incremento) determina el valor. +

+ +

+SpinType tiene los siguientes constructores: +

+ + + +

+muchos de estos constructores usan los valores del objeto Adjustment que está asociado con +el botón aumentar/disminuir. SpinStepForward y SpinStepBackward +cambian el valor del botón aumentar/disminuir en la cantidad indicada por el incremento, a no ser +que valga 0, en cuyo caso el valor será cambiado por el stepIncrement del ajuste. +SpinPageForward y SpinPageBackward cambian el valor del +SpinButton en el incremento. +SpinPageHome y SpinPageEnd establecen el valor mínimo +y máximo respectivamente del rango del Adjustment. +SpinUserDefined modifica el valor del botón aumentar/disminuir en la cantidad +especificada.

+ +

+Los botones aumentar/disminuir tienen una política de actualizaciones: +

+ +
spinButtonUpdatePolicy :: SpinButtonClass self => Attr self SpinButtonUpdatePolicy
+
+ +

+Los constructores de SpinButtonUpdatePolicy pueden ser +UdateAlways o UpdateIfValid. Estas políticas afectan +al comportamiento del SpinButton al analizar (procesar) el texto +insertado y sincronizar su valor con los valores del Adjustment. En el +caso de UpdateIfValid (actualiza si es válido) el valor del botón aumentar/disminuir sólo +se cambia si la entrada de texto es un valor numérico dentro del rango especificado en +el Adjustment. Si no es así se mantiene el valor actual. En caso de +UpdateAlways ignoramos los errores de conversión del "texto" en un valor numérico. +

+ +

+Finalmente, puedes solicitar que el propio SpinButton se actualice: +

+ +
spinButtonUpdate :: SpinButtonClass self => self -> IO ()
+
+ +

+Es el momento de volver a los ejemplos. Esta es una imagen después de juguetear +con algunos de los atributos:

+ +

botón aumentar/disminuir example

+ +

+Todos los botones aumentar/disminuir han sido creados con la siguiente función. Usa la +función spinButtonNewWithRange. Como el stepIncrement será 1.0 +en todos los casos, no es un parámetro de la función myAddSpinButton. +

+ +
myAddSpinButton :: HBox -> String -> Double -> Double -> IO SpinButton
+myAddSpinButton box name min max = do
+    vbox  <- vBoxNew False 0
+    boxPackStart box vbox PackRepel 0
+    label <- labelNew (Just name)
+    miscSetAlignment label 0.0 0.5
+    boxPackStart vbox label PackNatural 0
+    spinb <- spinButtonNewWithRange min max 1.0
+    boxPackStart vbox spinb PackNatural 0
+    return spinb
+
+ +

+En la función main usamos uno de los botones aumentar/disminuir existentes, +pero le damos un nuevo adjustment con spinButtonConfigure. Los +límites antiguos de -1000.0 y 1000.0 se reemplazan por -100.0 y 100.0. Fíjate en los +paréntesis que rodean a los números negativos. El valor inicial se establece en 0.0 y +el incremento de paso es de 0.25. El incremento de página, que es el que consigues al +pulsar el segundo botón del ratón en la flecha del botón aumentar/disminuir, se +establece en 10.0. El tamaño de página, que no se usa, es 0 aquí. Pulsando el tercer botón +del ratón en una de las flechas, salta hacia el límite correspondiente, -100.0 o 100.0. +

+ +

+Aquí, la nueva señal es onValueSpinned, que se emite siempre que el usuario +cambie el valor de un botón aumentar/disminuir. Aquí se usa para controlar el número de +dígitos decimales mostrados en el botón aumentar/disminuir spinLarge. +Fíjate en el redondeo del valor, necesario para convertir el tipo Double a un +tipo Integral. +

+ +

+En el ejemplo usamos las funciones genéricas get y set +en los atributos (hay funciones específicas para ello). Es un estilo de programación +recomendado en Gtk2Hs, ya que en el futuro las funciones específicas serán eliminadas. +

+ +
import Graphics.UI.Gtk
+
+main:: IO ()
+main = do
+    initGUI
+    window  <- windowNew
+    mainbox <- vBoxNew False 0
+    set window [windowTitle := "Spin buttons", containerBorderWidth := 10,
+                windowDefaultWidth := 250, windowDefaultHeight := 200,
+                containerChild := mainbox]
+    hbox1   <- hBoxNew False 0
+    frame1  <- frameNew
+    set frame1 [frameLabel := "Simple SpinButtons", containerChild := hbox1,
+                frameLabelYAlign := 0.8, frameShadowType := ShadowOut]
+    boxPackStart mainbox frame1 PackNatural 5
+
+    spinD <- myAddSpinButton hbox1 "Day:" 1.0 31.0
+    spinM <- myAddSpinButton hbox1 "Month:" 1.0 12.0
+    spinY <- myAddSpinButton hbox1 "Year:" 2000.0 2100.0
+    set spinY [spinButtonValue := 2007]
+
+    vbox1  <- vBoxNew False 5
+    frame2 <- frameNew
+    set frame2 [frameLabel := "More Features", containerChild := vbox1,
+                frameLabelYAlign := 0.8, frameShadowType:= ShadowOut]
+    boxPackStart mainbox frame2 PackNatural 5
+
+    hbox2 <- hBoxNew False 0
+    boxPackStart vbox1 hbox2 PackNatural 0
+
+    spinLarge <- myAddSpinButton hbox2 "Value:" (-1000.0) 1000.0
+    adj       <- adjustmentNew 0.0 (-100.0) 100.0 0.25 10.0 0.0
+    spinButtonConfigure spinLarge adj 0.0 2
+    spnctl    <- myAddSpinButton hbox2 "Decimal:" 0.0 10.0
+    set spnctl [spinButtonValue := 2.0]
+
+    tsnap <- checkButtonNewWithLabel "Snap to 0.25-ticks"
+    boxPackStart vbox1 tsnap PackNatural 0
+
+    tnumr <- checkButtonNewWithLabel "Numeric only input mode"
+    boxPackStart vbox1 tnumr PackNatural 0
+
+    twrap <- checkButtonNewWithLabel "Wraparound at limits"
+    boxPackStart vbox1 twrap PackNatural 0
+
+    widgetShowAll window
+
+    onValueSpinned spnctl $ do newdig <- get spnctl spinButtonValue
+                               set spinLarge [spinButtonDigits := (round newdig)]
+
+    onToggled tsnap $ do st <- get tsnap toggleButtonActive
+                         set spinLarge [spinButtonSnapToTicks := st]
+
+    onToggled tnumr $ do st <- get tnumr toggleButtonActive
+                         set spinLarge [spinButtonNumeric := st]
+
+    onToggled twrap $ do st <- get twrap toggleButtonActive
+                         set spinLarge [spinButtonWrap := st]
+
+    onDestroy window mainQuit
+    mainGUI
+
+ + + + hunk ./docs/tutorial/Tutorial_Port/es-index.xhtml 46 -