Hay APIs específicos para menús y toolbars, pero normalmente trabajarás
conjuntamente con ambos usando el
UIManager
para definir acciones que posteriormente situarás en
menús y barras de herramientas. Cada acción se puede asociar con varios
'proxy widgets'. De este modo puedes gestionar la activación de la acción en vez
de establecer una respuesta independiente a la acción de menú y al elemento del
toolbar. Además puedes habilitar o deshabilitar ambos items
con la acción.
actionNew :: String -- nombre : nombre único para la acción -> String -- etiqueta : lo que será mostrado en los elementos del menú y en los botones -> Maybe String -- tooltip : un tooltip para la acción -> Maybe String -- stockId : el stock item que se mostrará -> IO Action
Como puedes ver, una acción puede ser cualquier cosa. Cuando el usuario activa una acción, ya sea pulsando en el widget asociado o a través de una tecla de aceleración (lo vemos más adelante), se emite una señal. Se especifica lo que debe suceder con:
onActionActivate :: ActionClass self => self -> IO () -> IO (ConnectId self)
Una
Action
tiene métodos y atributos. Por ejemplo, puedes
ocultar una acción o hacerla insensible con:
actionSetVisible :: ActionClass self => self -> Bool -> IO () actionSetSensitive :: ActionClass self => self -> Bool -> IO ()
Sin embargo, las acciones de agrupan juntas, y una acción sólo puede ser visible (o sensible) si su grupo es visible (o sensible). Los grupos de acciones se crean con:
actionGroupNew :: String -> IO ActionGroup
El argumento es el nombre del
ActionGroup
y se usa cuando se asocian los key
bindings con las acciones. Para añadir acciones a un grupo, cuando no se usan teclas de
aceleración ni stocks items, usamos:
actionGroupAddAction ActionClass action => ActionGroup -> action -> IO ()
Si se usa una tecla de aceleración, o un stock item:
actionGroupAddActionWithAccel :: ActionClass action => ActionGroup -> action -> Maybe String -> IO ()
Si usas un stock item, el argumento
Maybe String
debe ser
Nothing
. Si no usas stock item, pero no especificas un acelerador, usa
Just ""
. En cualquier otro caso la cadena debe estar en un
formato que pueda ser analizado (lo veremos más adelante). Puedes establecer la visibilidad
y sensibilidad de un
ActionGroup
con:
actionGroupSetVisible :: ActionGroup -> Bool -> IO () actionGroupSetSensitive :: ActionGroup -> Bool -> IO ()
Como se ha dicho, una acción en un grupo sólo será visible (sensible) si lo es ella y el grupo al que pertenece.
Ahora puedes usar las acciones si las fijas (binding) a
uno o más widgets proxy, por ejemplo en un menú y en un toolbar.
Por supuesto que puedes asociarla a un único elemento, pero la idea tras
el diseño de acciones es la reusabilidad. Puedes hacer esto mediante
un String
en formato XML.
Los elementos XML disponibles son los siguientes: ui, menubar, menu, menuitem, toolbar, toolitem y popup. Los elementos menuitem y toolitem requieren un atributo de acción, y este se establece en el nombre único que recibió la acción cuando fue creada. Los elementos menubar y toolbar también pueden tener acciones asociadas con ellos, pero son opcionales. Todos los elementos pueden tener nombres, y esos son también opcionales. Necesitamos los nombres para distinguir los widgets del mismo tipo y con la misma dirección, por ejemplo dos barras de herramientas justo por debajo de root (raíz) (dentro de los elementos ui (interfaz de usuario)).
Además dispones de elementos separadores, colocadores y aceleradores. Los separadores son líneas en las barras de herramientas y en las barras de menús. Los colocadores se utilizan para agrupar elementos y sub árboles y los aceleradores definen las teclas de aceleración. La referencia de GTK+ advierte que no deben confundirse los aceleradores con los mnemónicos. Los mnemónicos se activan a través de una letra en la etiqueta, mientras que los aceleradores se activan a través de una combinación de teclas que tú especificas.
Nota: Desafortunadamente los aceleradores para los menús y toolbars no me han funcionado como se anuncia. Quizá esto sea debido a GTK+, Gtk2Hs, la plataforma, o quizá debido a que no he entendido algo. En cualquier caso intentalo a ver si tienes más suerte.
La sección Graphics.UI.Gtk.ActionMenuToolbar.UIManager en la documentación de la API contiene un DTD (Document Type Definition - Definición de tipo de documento) para la cadena XML, así como alguna información adicional sobre su formato.
Aquí tienes un ejemplo de la cadena XML, que hemos usado en el ejemplo inferior. Las barras al principio y final de cada línea se necesitan para que GHCi y GHC sepan que la línea continúa y que debe entender las " como parte de la cadena y no como delimitadores de la misma. La indentación no tiene un especial significado aquí.
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>"
Todos los atributos de la acción son cadenas que hemos definido antes, cuando hemos creado las acciones (puedes verlo en el listado completo del programa al final del texto).
Ahora la definición debe ser procesada por un gestor de ui. Para crear uno:
uiManagerNew :: IO UIManager
Para añadir la cadena XML:
uiManagerAddUiFromString :: UIManager -> String -> IO MergeId
Después, los grupos de acción, que ya han sido creados, deben ser insertados:
uiManagerInsertActionGroup :: UIManager -> ActionGroup -> Int -> IO ()
Si sólo tienes un grupo de acción, la posición será 0, en cualquier otro caso tienes que especificar el índice en la lista que ya tienes.
Ahora ya puedes conseguir todos los widgets que necesites de tu
UIManager
y la dirección (path) (incluyendo nombres si
fuera necesario) en tu definición XML.
uiManagerGetWidget :: UIManager -> String -> IO (Maybe Widget)
De la definición anterior, por ejemplo, poedemos conseguir una barra de manú y una barra de herramientas con:
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
El empaquetado ha sido incluído en el snippet anterior, para demostrar que esto debes hacerlo tú. Este es el ejemplo con el código:
Hemos colocado una acción como insensible, para mostrar como se hace.
También hemos añadido un acelerador a la acción de salir (exit), que emplea el
stockitem stockQuit
pero ahora muestra el acelerador Ctrl + E. Según el
manual de referencia de GTK+, las teclas de aceleración se definen:
<Control>a, <Shift><Alt>F1, <Release>z, etcétera. Como ya he indicado,
las teclas de aceleración se muestran, pero en mi configuración no funcionan. fíjate que en
este ejemplo hemos usado mapM_
en vez de la combinación sequence_
y map
de los capítulos precedentes.
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 "<Control>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)