Tutoriel Gtk2Hs

Il y a des API spécifiques pour les menus et les barres d’outils, mais il est conseillé de les utiliser ensemble avec UIManager pour définir les actions que vous pouvez alors disposer dans des menus et des barres d’outils. Chaque action peut être associée avec plusieurs widgets. De cette façon, vous pouvez gérer l’activation de l’action au lieu de gérer les entrées des menus et des barres d’outils. Vous pouvez activer ou désactiver le menu et la barre d’outils avec l’action:

actionNew
  :: String              --  name : a unique name for the action
  -> String              --  label : what will be displayed in menu items and on buttons
  -> Maybe String        --  tooltip : a tooltip for the action
  -> Maybe String        --  stockId : the stock item to be displayed
  -> IO Action

Comme vous pouvez le voir, une action peut être n’importe quoi. Quand l’utilisateur active une action en cliquant sur un widget qui lui est associé ou par un raccourci clavier (voir plus loin), une signal est émis est vous définissez ce qui se produit alors avec :

onActionActivate :: ActionClass self => self -> IO () -> IO (ConnectId self)

Une Action a des méthodes et des attributs. Par exemple vous pouvez cacher une action ou la rendre inactive avec :

actionSetVisible :: ActionClass self => self -> Bool -> IO ()
actionSetSensitive :: ActionClass self => self -> Bool -> IO ()

Cependant, les actions sont groupées ensemble et une action ne peut être visible (activable) que si le groupe auquel elle appartient est visible (activable). On crée un nouveau groupe d’action avec :

actionGroupNew :: String -> IO ActionGroup

L’argument est le nom du ActionGroup et il est utilisé quand on associe des raccourcis claviers avec les actions. Pour ajouter des actions à un groupe, quand aucun raccourci clavier n’est utilisé et sans stock item :

actionGroupAddAction ActionClass action => ActionGroup -> action -> IO ()

Si un raccourci clavier est utilisé, ou un stock item:

actionGroupAddActionWithAccel :: ActionClass action => ActionGroup -> action -> Maybe String -> IO ()

Si vous utilisez un stock item, le paramètre Maybe String doit être Nothing. Si vous n’utilisez pas un stock item et que vous n’utilisez pas un raccourci, utilisez Just . D’autre part, la chaîne de caractères doit être dans un format qui permet son analyse (voir plus loin). Vous pouvez définir la visibilité et le caractère activable d’un ActionGroup avec :

actionGroupSetVisible :: ActionGroup -> Bool -> IO ()
actionGroupSetSensitive :: ActionGroup -> Bool -> IO ()

Comme nous l’avons dit, une action dans un groupe n’est visible que si son attribut et l’attribut de son groupe sont définis.

Maintenant, vous pouvez utiliser les actions en les liant à un ou plusieurs widgets, par exemple dans un menu ou une barre d’outils. Bien sur vous pouvez l’attacher à un seul widget mais l’idée est de les réutiliser. Vous pouvez le faire avec une chaîne de caractères au format XML.

Les éléments XML autorisés sont ui, menubar, menu, menuitem, toolbar, toolitem et popup. Les éléments menuitem et toolitem requièrent une action, et ils doivent être associés au nom (unique) que vous avez donné à l’action lorsque vous l’avez créée. Les éléments de la barre de menus et d’outils peuvent aussi avoir des actions qui leurs sont associées, mais elles sont optionnelles. Tous les éléments peuvent avoir des noms, et ils sont également optionnels. Les noms sont requis pour distinguer des widgets du même type et avec le même chemin, par exemple deux barres d’outils juste sous la racine (l’élément ui).

En complément vous avez les séparateurs, les espaces réservés et les accélérateurs. Les séparateurs apparaissent comme des lignes dans les barres d’outils et de menus. Les espaces réservés peuvent être utilisés pour grouper des éléments et des sous-arbres et les accélérateurs définissent des raccourcis clavier. Le livre de référence GTK+ signale que les accélérateurs ne doivent pas être confondus avec les mnémoniques. Les mnémoniques sont activés par une lettre dans l’étiquette, les accélérateurs sont activés par une combinaison que vous avez spécifiée.

Note: Unfortunately the accelerators for action menus and toolbars do not seem to work as advertised. Whether this is due to GTK+, Gtk2Hs, the platform, or because I’ve missed something, is not clear to me. You’ll just have to try it out!

La section Graphics.UI.Gtk.ActionMenuToolbar.UIManager dans l’API contient un document type pour la chaine XML, ainsi que des informations complémentaires sur le formatage.

Voici un exemple d’une String XML, qui est utilisé dans l’exemple. Les slashes au début et à la fin de chaque ligne sont nécessaire pour indiquer à GHCI et GHC que la châine continue ici et les guillemets dans la définition XML doivent aussi être échappés. Les indentations n’ont pas d’utilité ici :

uiDecl=  "<ui>\
\           <menubar>\
\            <menu action=\"FMA\">\
\              <menuitem action=\"NEWA\" />\
\              <menuitem action=\"OPNA\" />\
\              <menuitem action=\"SAVA\" />\
\              <menuitem action=\"SVAA\" />\
\              <separator />\
\              <menuitem action=\"EXIA\" />\
\            </menu>\
\           <menu action=\"EMA\">\
\              <menuitem action=\"CUTA\" />\
\              <menuitem action=\"COPA\" />\
\              <menuitem action=\"PSTA\" />\
\           </menu>\
\            <separator />\
\            <menu action=\"HMA\">\
\              <menuitem action=\"HLPA\" />\
\            </menu>\
\           </menubar>\
\           <toolbar>\
\            <toolitem action=\"NEWA\" />\
\            <toolitem action=\"OPNA\" />\
\            <toolitem action=\"SAVA\" />\
\            <toolitem action=\"EXIA\" />\
\            <separator />\
\            <toolitem action=\"CUTA\" />\
\            <toolitem action=\"COPA\" />\
\            <toolitem action=\"PSTA\" />\
\            <separator />\
\            <toolitem action=\"HLPA\" />\
\           </toolbar>\
\          </ui>" </pre>"

Tous les attributs d’action sont des chaînes qui sont définies plus tôt, quand les actions sont créées (Voir le code source complet plus loin).

Maintenant, cette définition ou déclaration doit être traitée par un gestionnaire UIManager Pour en créer un:

uiManagerNew :: IO UIManager

Pour ajouter la chaîne XML:

uiManagerAddUiFromString :: UIManager -> String -> IO MergeId

Ensuite, les actions qui ont été créées auparavant doivent être insérées:

uiManagerInsertActionGroup :: UIManager -> ActionGroup -> Int -> IO ()

Si vous avez seulement un groupe d’action, la position sera 0, autrement vous devrez spécifier l’index dans la liste que vous avez déjà créée.

Vous pouvez maintenant avoir tous les widgets que vous voulez à partir de votre UIManager et du chemin (incluant les noms si nécessaire) dans votre définition en XML.

uiManagerGetWidget :: UIManager -> String -> IO (Maybe Widget)

A partie de la définition ci-dessus, on peut avoir une barre de menus et une barre d’outils avec:

maybeMenubar <- uiManagerGetWidget ui "/ui/menubar"
     let menubar = case maybeMenubar of
                        (Just x) -> x
                        Nothing -> error "Cannot get menubar from string." 
     boxPackStart box menubar PackNatural 0

     maybeToolbar <- uiManagerGetWidget ui "/ui/toolbar"
     let toolbar = case maybeToolbar of
                        (Just x) -> x
                        Nothing -> error "Cannot get toolbar from string." 
     boxPackStart box toolbar PackNatural 0

L’empaquetage a été inclu dans le morceau de code, pour montrer qu’il doit malgré tout être fait par vous. Voici l’exemple complet:

Une action a été paramétrée pour être inactive afin de montrer la marche à suivre. Un accélérateur a également été ajouté à l’action de sortie, qui prend le stockitem stockQuit mais affiche Ctrl+E comme accélérateur. Selon le manuel de référence de GTK+, les touches accélératrices sont : , F1, .

import Graphics.UI.Gtk

main :: IO ()
main = do
     initGUI
     window <- windowNew
     set window [windowTitle := "Menus and Toolbars",
                 windowDefaultWidth := 450, windowDefaultHeight := 200]

     box <- vBoxNew False 0
     containerAdd window box

     fma <- actionNew "FMA" "File" Nothing Nothing
     ema <- actionNew "EMA" "Edit" Nothing Nothing
     hma <- actionNew "HMA" "Help" Nothing Nothing

     newa <- actionNew "NEWA" "New"     (Just "Just a Stub") (Just stockNew)
     opna <- actionNew "OPNA" "Open"    (Just "Just a Stub") (Just stockOpen)
     sava <- actionNew "SAVA" "Save"    (Just "Just a Stub") (Just stockSave)
     svaa <- actionNew "SVAA" "Save As" (Just "Just a Stub") (Just stockSaveAs)
     exia <- actionNew "EXIA" "Exit"    (Just "Just a Stub") (Just stockQuit)
 
     cuta <- actionNew "CUTA" "Cut"   (Just "Just a Stub") (Just stockCut)    
     copa <- actionNew "COPA" "Copy"  (Just "Just a Stub") (Just stockCopy)
     psta <- actionNew "PSTA" "Paste" (Just "Just a Stub") (Just stockPaste)

     hlpa <- actionNew "HLPA" "Help"  (Just "Just a Stub") (Just stockHelp)

     agr <- actionGroupNew "AGR"
     mapM_ (actionGroupAddAction agr) [fma, ema, hma]
     mapM_ (\ act -> actionGroupAddActionWithAccel agr act Nothing) 
       [newa,opna,sava,svaa,cuta,copa,psta,hlpa]

     actionGroupAddActionWithAccel agr exia (Just "e")

     ui <- uiManagerNew
     uiManagerAddUiFromString ui uiDecl
     uiManagerInsertActionGroup ui agr 0

     maybeMenubar <- uiManagerGetWidget ui "/ui/menubar"
     let menubar = case maybeMenubar of
                        (Just x) -> x
                        Nothing -> error "Cannot get menubar from string." 
     boxPackStart box menubar PackNatural 0

     maybeToolbar <- uiManagerGetWidget ui "/ui/toolbar"
     let toolbar = case maybeToolbar of
                        (Just x) -> x
                        Nothing -> error "Cannot get toolbar from string." 
     boxPackStart box toolbar PackNatural 0

     actionSetSensitive cuta False

     onActionActivate exia (widgetDestroy window)
     mapM_ prAct [fma,ema,hma,newa,opna,sava,svaa,cuta,copa,psta,hlpa]

     widgetShowAll window
     onDestroy window mainQuit
     mainGUI
     
uiDecl=  "<ui>\
\           <menubar>\
\            <menu action=\"FMA\">\
\              <menuitem action=\"NEWA\" />\
\              <menuitem action=\"OPNA\" />\
\              <menuitem action=\"SAVA\" />\
\              <menuitem action=\"SVAA\" />\
\              <separator />\
\              <menuitem action=\"EXIA\" />\
\            </menu>\
\           <menu action=\"EMA\">\
\              <menuitem action=\"CUTA\" />\
\              <menuitem action=\"COPA\" />\
\              <menuitem action=\"PSTA\" />\
\           </menu>\
\            <separator />\
\            <menu action=\"HMA\">\
\              <menuitem action=\"HLPA\" />\
\            </menu>\
\           </menubar>\
\           <toolbar>\
\            <toolitem action=\"NEWA\" />\
\            <toolitem action=\"OPNA\" />\
\            <toolitem action=\"SAVA\" />\
\            <toolitem action=\"EXIA\" />\
\            <separator />\
\            <toolitem action=\"CUTA\" />\
\            <toolitem action=\"COPA\" />\
\            <toolitem action=\"PSTA\" />\
\            <separator />\
\            <toolitem action=\"HLPA\" />\
\           </toolbar>\
\          </ui>" </pre>"


prAct :: ActionClass self => self -> IO (ConnectId self)
prAct a = onActionActivate a $ do name <- actionGetName a
                                  putStrLn ("Action Name: " ++ name)