[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 - + + + +
+ +
+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.
+
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!):
+
onPressed
- se emite cuando el ratón es pulsado dentro del widget Button
+ onReleased
- se emite cuando el ratón se deja de pulsar dentro del widget Button
+ onClicked
- se emite cuando el ratón se pulsa y se suelta dentro del widget Button
+ onEnter
- se emite cuando el ratón entra en el widget Button
+ onLeave
- se emite cuando el ratón sale del widget Button
+ +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. +
+ + ++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.
+
+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.
+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.
+
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 - + + + + + + + +
+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
.
+
+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
.
+
+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 )onRangeValueChanged
se emite continuamente,
+ p.ej., cuando el deslizador se mueve un valor mínimo.
+ UpdateDiscontinuous
(Actualización discontinua)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)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 () ++ +
+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.
+
+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.
+
+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.
+
+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 - + + + + + + + +
+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:
+
JustifyLeft
IzquierdaJustifyRight
DerechaJustifyCenter
CentradoJustifyFill
Ambos+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 Int
s (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.
+
+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 - + + + + +
+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:
+
ArrowUp
ArribaArrowDown
AbajoArrowLeft
IzquierdaArrowRight
DerechaArrowNone
Ninguno
+El ShadowType
(tipo de sombra) también tiene cinco constructores:
+
ShadowIn
DentroShadowOut
FueraShadowEtchedIn
Grabado dentroShadowEtchedOut
Grabado fueraShadowNone
Sin sombra+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. +
+ ++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 - + + + + + +
+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 StockItem
s son recursos conocidos en Gtk2Hs, como los IconSet
s 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
.
+
+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 - + + + + +
+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 ContextId
s 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.
+ +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 - + + + + + +
+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:
+
SpinButton
+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:
+
SpinStepForward
Paso adelanteSpinStepBackward
Paso atrásSpinPageForward
Página adelanteSpinPageBackward
Página atrásSpinHome
InicioSpinEnd
FinSpinUserDefined
Definido por usuario
+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:
+ +
+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 -