Tutoriel Gtk2Hs

6.2 Boites d’événements et boites à boutons

Dans Gtk2Hs, un événement est quelque chose qui est envoyé à un widget par la boucle principale, habituellement en résultat d’une action effectuée par l’utilisateur. Le widget répond en retour en émettant un signal et c’est ce signal qui commande au programme de faire quelque chose. Pour le programmeur d’application Gtk2Hs, un événement est juste un type de donnés Haskell avec des champs nommés. Certains d’entre eux sont décrits dans la section Graphics.UI.Gtk.Gdk.Events de la documentation de l’API. Regardons par exemple le signal:

onButtonPress :: WidgetClass w => w -> (Event -> IO Bool) -> IO (ConnectId w)

Ce signal ne doit pas être confondus avec le signal émis quand un widget de type Button a été pressé. Le bouton dont on parle ici est le bouton de la souris . Le signal est émis quand un bouton de la souris est pressé quand celle-ci se trouve au dessus de ce widget.Le paramètre est une fonction qui prend un événement (qui doit avoir le constructeur Button) et retourne une valeur booléenne et une action d’entrée sortie IO. Les champs suivants pour Button sont tirés de l’API:

eventSent :: Bool
eventClick :: Click
eventTime :: TimeStamp
eventModifier :: [Modifier]
eventButton :: MouseButton
eventXRoot, eventYRoot :: Double

Le premier est utilisé pour le retour. Il apparaît dans tous les constructeurs Event tels que Motion, Expose, Key, Crossing, Focus, Configure, Scroll, WindowState, Proximity. De Event vous pouvez extraire toutes les informations concernant les actions de l’utilisateur. Voici un petit exemple:

onButtonPress eb 
                 (\x -> if (eventButton x) == LeftButton 
                           then do widgetSetSensitivity eb False 
                                   return (eventSent x)
                           else return (eventSent x))

Ici, le paramètre eb est le widget sous la souris et la fonction anonyme est du type décrit précédemment. Quelque chose est fait si le bouton gauche de la souris a été pressé et quand eventSent retourne la valeur booléenne attendue. Si un autre bouton de la souris est pressé, rien ne se produit et seul le booléen est retourné.

Maintenant, certains widgets n’ont pas de fenêtre associée et se contente de dessiner dans leur widget . A cause de cela, ils ne peuvent pas recevoir d’événements et si ils sont mal dimenssionnés, ils n’afficheront pas correctement le contenu dans la zone dédiée. Le widget EventBox fournit une fenêtre X pour ces widgets . Il s’agit d’une sous-classe de Bin qui possède également sa propre fenêtre et qui est une sous-classe de ContainerClass.

Pour créer une nouvelle EventBox, utilisez:

eventBoxNew :: IO EventBox

Pour ajouter un enfant, on utilise simplement:

containerAdd :: (ContainerClass self, WidgetClass widget) => self -> widget -> IO ()

La fenêtre peut être visible ou invisible et la boite d’événement peut être au dessus ou en dessous de l’enfant. Ceci est définit par:

eventBoxVisibleWindow :: Attr EventBox Bool    -- default True
eventBoxAboveChild :: Attr EventBox Bool       -- default False

Si vous voulez juste capturer des événements, alors la fenêtre sera rendue invisible. Si eventBox est au dessus de l’enfant, tous les événements iront au premier. Si elle est en dessous les événement iront au widget en premier.

Une boite à boutons est une simple boite qui peut être utilisée pour empaqueter des boutons d’une façon standard. Il y en a de deux sortes, les horizontales et les verticales, et on les construit avec:

hbuttonBoxNew :: IO HButtonBox
vButtonBoxNew :: IO VButtonBox

buttonBoxSetLayout :: ButtonBoxClass self => self -> ButtonBoxStyle -> IO ()

Le style est un de ceux-ci : ButtonBoxDefaultStyle, ButtonBoxSpread, ButtonBoxEdge, ButtonBoxStart, ButtonBoxEnd. On n’empaquette pas les boutons comme dans une boite horizontale ou verticale standard, mais en utilisant la fonction containerAdd à la place.

La seconde fonctionnalité d’une boite à boutons est que l’on peut définir un ou plusieurs boutons pour être dans un groupe secondaire. Ceux-ci seront traités différemment lorsque la boite à boutons est redimensionnée. Par exemple, un bouton d’aide peut être isolé (visuellement) des autres. La fonction est:

buttonBoxSetChildSecondary :: (ButtonBoxClass self, WidgetClass child) => self -> child -> Bool -> IO ()

Cet exemple montre l’utilisation des boites d’événements et des boites à bouton:

Gtk Gtk2Hs Slot Machine

Les boutons sont empaquetés dans une boite à bouton verticale avec le bouton play comme deuxième . Ce bouton contient aussi un mnémonique avec la combinaison de touches Alt-P. Les images sont placées dans des boites d’événements avec des fenêtres visibles et leur couleur de fond est définie avec:

widgetModifyBg eb StateNormal (Color 0 35000 0)

Comme mentionné dans le chapitre 5.3, StateType peut être StateNormal, StateActive, StatePrelight, StateSelected ou StateInsensitive.

Lorsque l’utilisateur clique le bouton gauche de la souris quand elle est au dessus d’une boite d’évènement, elle sera définie à StateInsensitive avec:

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

Cela change le StateType à StateInsensitive et le widget ne répondra plus à aucun événement utilisateur. De plus, son apparence change. Dans l’exemple, on définit également la couleur de fond à gris clair.

Gtk Gtk2Hs Slot Machine Insensitive
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" ""