Un evento en Gtk2Hs es algo que se envía a un widget, por el bucle principal, normalmente como resultado de una acción realizada por el usuario. El widget entonces responde emitiendo una señal, y esta es la señal para el programa de hacer algo. Para el programador de aplicaciones de Gtk2Hs, sin embargo, un evento no es más que un tipo de datos de Haskell con campos nombrados. La mayoría de estos están descritos en la sección de la API correspondiente a Graphics.UI.Gtk.Gdk.Events. Mira, por ejemplo, la señal del widget:
onButtonPress :: WidgetClass w => w -> (Event -> IO Bool) -> IO (ConnectId w)
Esto no debe ser confundido con la señal emitida cuando un
widget de tipo Button
es presionado; el botón
aquí es un botón del ratón y la señal se emite cuando se ha pulsado
un botón del ratón cuando el ratón estaba sobre ese widget. El manejador es una función
que toma un evento, que tiene que tener el constructor
Button
, y tiene un valor IO boolean. La
API lista los siguientes campos para
Button
:
eventSent :: Bool eventClick :: Click eventTime :: TimeStamp eventModifier :: [Modifier] eventButton :: MouseButton eventXRoot, eventYRoot :: Double
El primero se usa para el retorno. Ocurre en todos los constructores
Event
como Motion, Expose, Key, Crossing, Focus, Configure, Scroll,
WindowState and Proximity.
(movimiento, exposición, llave, cruce, foco, configurar,
desplazamiento, estado de la ventana y proximidad). De los
Events
puedes obtener todo tipo de información sobre lo que está haciendo el
usuario. Tienes un ejemplo sencillo en este trozo de código:
onButtonPress eb (\x -> if (eventButton x) == LeftButton then do widgetSetSensitivity eb False return (eventSent x) else return (eventSent x))
Aquí, el parámetro
eb
es el widget sobre el que está el ratón, y la
función anónima es del tipo descrito arriba. Algo se hace
(mira el ejemplo inferior) si el botón izquierdo del ratón ha
sido pulsado y entonces,
eventSent
devuelve el booleano apropiado.
si se pulsa cualquier otro botón del ratón, no pasa nada
y sólo se devuelve el booleano.
Ahora, algunos widwets no tienen ventanas asociadas, así
que están dibujados en sus ventanas padre. Por eso no pueden recibir eventos
y si su tamaño está puesto incorrectamente, no se superponen con lo
que puedes organizar un lío de sobreescritura (no seguiremos discutiendo este aspecto). Un
EventBox
proporciona una ventana X window para su widget hijo.
Es una subclase de Bin
que también tiene su propia ventana y que es una subclase de
ContainerClass
.
Para crear un nuevo widget EventBox widget, usa:
eventBoxNew :: IO EventBox
Para añadir un hijo simplemente tenemos que usar el bien conocido:
containerAdd :: (ContainerClass self, WidgetClass widget) => self -> widget -> IO ()
La ventana puede ser visible o invisible, y la caja de eventos puede estar por encima o por debajo de su hijo en el árbol del widget. Esto se determina por:
eventBoxVisibleWindow :: Attr EventBox Bool -- default True eventBoxAboveChild :: Attr EventBox Bool -- default False
Si simplemente quieres capturar los eventos, entonces establece la
ventana como invisible. Si el
eventBox
está sobre su hijo, todos los eventos irán en primer lugar allí primero.
Si está debajo, las ventanas en los widgets hijos del hijo serán
alcanzados primero.
Una caja Button es simplemente una caja que puede ser usada para empaquetar botones de un modo estándar. Hay dos tipos, horizontales and verticales, y puedes construirlos con:
hbuttonBoxNew :: IO HButtonBox vButtonBoxNew :: IO VButtonBox
La funcionalidad está en el
ButtonBoxClass.
buttonBoxSetLayout :: ButtonBoxClass self => self -> ButtonBoxStyle -> IO ()
El estilo es uno de los siguientes:
ButtonBoxDefaultStyle, ButtonBoxSpread, ButtonBoxEdge,
ButtonBoxStart, ButtonBoxEnd
. No se empaquetan los buttons como
en las cajas normales verticales y horizontales, sino que debes usar la
función containerAdd
.
La segunda característica de las cajas button boxes es que puedes definir que uno o más de los botones estén en un grupo secundario. Estos serán tratados de modo diferente cuando se modifique el tamaño de la caja button. Por ejemplo, un botón de ayuda puede mantenerse visualmente separado de los otros. La función es:
buttonBoxSetChildSecondary :: (ButtonBoxClass self, WidgetClass child) => self -> child -> Bool -> IO ()
Esto ilustra el uso de cajas de eventos y cajas de botón:
Los botones se empaquetan en una caja de botones verticales, con el botón play como un hijo secundario. Además es un botón mnemónico, con Alt-P como tecla aceleradora. Las imágenes se colocan en cajas de eventos con ventanas visibles, y su color de fondo se establece en un color verde con:
widgetModifyBg eb StateNormal (Color 0 35000 0)
Como se mencionó en el capítulo 5.3 el
StateType
puede ser
StateNormal, StateActive, StatePrelight, StateSelected or
StateInsensitive
(normal, activo, preiluminado, seleccionado o insensible) .
Fíjate en que las imágenes de abajo no tienen todas el mismo tamaño. No tiene especial importancia, pero debemos tener cuidado de asegurarnos de que la ventana principal sea suficientemente grande. De otro modo los bordes desaparecerán cuando se cambien las imágenes.
Cuando el usuario pulsa el botón izquierdo del ratón cuando el ratón está sobre una caja de eventos, se puede colocar en el estado insensible con:
widgetSetSensitivity :: WidgetClass self => self -> Bool -> IO ()
Esto cambia el StateType
a
StateInsensitive
y el widget no responderá más a los
eventos del usuario. Ademas su apariencia cambia.
En el ejemplo hemos cambiado también el color de fondo a
un gris.
Hemos usado tooltips para indicar al usuario que las imágenes pueden ser congeladas.
Como se mencionó en el Capítulo 4.4 no siempre funcionan con el intérprete pero si lo
hacen con el compilador. Para cambiar las imágenes aleatoriamente, usamos la función
RandomRIO, como en el capítulo anterior. Te puede sorprender el porque hemos usado una tupla de
EventBox
y
Image
, en vez de simplemente usar la
Image
del atributo
containerChild
de las cajas de evento. Esto se debe a que es
un atributo de sólo escritura, puede
set
(establecerse) pero no recuperarse con
get
.
Finalmente, si no puedes disponer de las imágenes en tu directorio de código fuente, o si quieres extender la máquina con más posibilidades, tienes un gran surtido de peces brasileños en peces. Están clasificados en peces de agua salada (água salgado) y de agua dulce (água doce).
import Graphics.UI.Gtk import System.Random (randomRIO) main :: IO () main= do initGUI window <- windowNew set window [windowTitle := "Slot Machine", containerBorderWidth := 10, windowDefaultWidth := 350, windowDefaultHeight := 400] hb1 <- hBoxNew False 0 containerAdd window hb1 vb1 <- vBoxNew False 0 boxPackStart hb1 vb1 PackGrow 0 vbb <- vButtonBoxNew boxPackStart hb1 vbb PackGrow 0 resetb <- buttonNewWithLabel "Reset" containerAdd vbb resetb quitb <- buttonNewWithLabel "Quit" containerAdd vbb quitb playb <- buttonNewWithMnemonic "_Play" containerAdd vbb playb set vbb [buttonBoxLayoutStyle := ButtonboxStart, (buttonBoxChildSecondary playb) := True ] let picfiles = ["./jacunda.gif", "./pacu.gif", "./tucunaream.gif"] evimls <- sequence (map (initEvent vb1) picfiles) tips <- tooltipsNew sequence_ $ map ((myTooltip tips) . fst) evimls onClicked playb (play evimls picfiles) onClicked resetb $ sequence_ (zipWith reSet evimls picfiles) onClicked quitb (widgetDestroy window) widgetShowAll window onDestroy window mainQuit mainGUI initEvent :: VBox -> FilePath -> IO (EventBox, Image) initEvent vb picfile = do eb <- eventBoxNew boxPackStart vb eb PackGrow 0 slot <- imageNewFromFile picfile set eb[containerChild := slot, containerBorderWidth := 10 ] widgetModifyBg eb StateNormal (Color 0 35000 0) widgetModifyBg eb StateInsensitive (Color 50000 50000 50000) onButtonPress eb (\x -> if (eventButton x) == LeftButton then do widgetSetSensitivity eb False return (eventSent x) else return (eventSent x)) return (eb, slot) reSet :: (EventBox, Image) -> FilePath -> IO () reSet (eb, im) pf = do widgetSetSensitivity eb True imageSetFromFile im pf play :: [(EventBox, Image)] -> [FilePath] -> IO () play eilist fplist = do let n = length fplist rands <- sequence $ replicate n (randomRIO (0::Int,(n-1))) sequence_ (zipWith display eilist rands) where display (eb, im) rn = do state <- widgetGetState eb if state == StateInsensitive then return () else imageSetFromFile im (fplist !! rn) myTooltip :: Tooltips -> EventBox -> IO () myTooltip ttp eb = tooltipsSetTip ttp eb "Click Left Mouse Button to Freeze" ""