Los Menús normalmente pertenecen a una ventana, pero pueden ser mostrados temporalmente como resultado de la pulsación de un botón del ratón. Por ejemplo, puede mostrarse un menú de contexto cuando el usuario pulsa el botón derecho de su ratón.
La disposición de un menú popup menu debe usar el nodo
popup
. Por ejemplo:
uiDecl = "<ui> \ \ <popup>\ \ <menuitem action=\"EDA\" />\ \ <menuitem action=\"PRA\" />\ \ <menuitem action=\"RMA\" />\ \ <separator />\ \ <menuitem action=\"SAA\" />\ \ </popup>\ \ </ui>"
La construcción de un menú popup lleva los mismos pasos que la construcción de un menú o un toolbar.(pero... sigue leyendo). Una vez que has creado las acciones y las has puesto en uno o más grupos, creas el gestor de UI, le añades la cadena XML y le añades los grupos. Es el momento de extraer el(los) widget(s). En nuestro ejemplo de popup hemos creado 4 acciones con los nombres listados arriba. La ventana de popup no se muestra en un volcado de pantalla por lo que hemos omitido la imagen.
Como es un popup no hemos empaquetado el widget. Para mostralo, necesitamos la función:
menuPopup :: MenuClass self => self -> Maybe (MouseButton,TimeStamp)
Todo esto está en la documentación de la API referente al módulo Graphics.UI.Gtk.MenuComboToolbar.Menu.
En el ejemplo, el menú aparece cuando pulsamos el botón derecho del ratón,
el segundo argumento puede ser
Nothing
. La función es la misma que la que vimos en el
capítulo 6.2. Aquí, sin embargo, podemos usar la ventana en vez de
una caja de eventos.
onButtonPress window (\x -> if (eventButton x) == RightButton then do menuPopup (castToMenu pop) Nothing return (eventSent x) else return (eventSent x))
El único truco es que el widget devuelto por el gestor de ui es de
tipo Widget
y la función menuPopup
necesita un argumento
de un tipo que sea una instancia de MenuClass
. Así que tenemos que usar:
castToMenu :: GObjectClass obj => obj -> Menu
Esta función también está documentada en la sección Graphics.UI.Gtk.MenuComboToolbar.Menu. El listado completo del ejemplo es:
import Graphics.UI.Gtk main :: IO () main= do initGUI window <- windowNew set window [windowTitle := "Click Right Popup", windowDefaultWidth := 250, windowDefaultHeight := 150 ] eda <- actionNew "EDA" "Edit" Nothing Nothing pra <- actionNew "PRA" "Process" Nothing Nothing rma <- actionNew "RMA" "Remove" Nothing Nothing saa <- actionNew "SAA" "Save" Nothing Nothing agr <- actionGroupNew "AGR1" mapM_ (actionGroupAddAction agr) [eda,pra,rma,saa] uiman <- uiManagerNew uiManagerAddUiFromString uiman uiDecl uiManagerInsertActionGroup uiman agr 0 maybePopup <- uiManagerGetWidget uiman "/ui/popup" let pop = case maybePopup of (Just x) -> x Nothing -> error "Cannot get popup from string" onButtonPress window (\x -> if (eventButton x) == RightButton then do menuPopup (castToMenu pop) Nothing return (eventSent x) else return (eventSent x)) mapM_ prAct [eda,pra,rma,saa] widgetShowAll window onDestroy window mainQuit mainGUI uiDecl = "<ui> \ \ <popup>\ \ <menuitem action=\"EDA\" />\ \ <menuitem action=\"PRA\" />\ \ <menuitem action=\"RMA\" />\ \ <separator />\ \ <menuitem action=\"SAA\" />\ \ </popup>\ \ </ui>" prAct :: ActionClass self => self -> IO (ConnectId self) prAct a = onActionActivate a $ do name <- actionGetName a putStrLn ("Action Name: " ++ name)
Hay otro modo de usar las acciones, sin crearlas específicamente, a
partir del tipo de datos ActionEntry
:
data ActionEntry = ActionEntry { actionEntryName :: String actionEntryLabel :: String actionEntryStockId :: (Maybe String) actionEntryAccelerator :: (Maybe String) actionEntryTooltip :: (Maybe String) actionEntryCallback :: (IO ()) }
El uso de estos campos es como su nombre indica y como ha sido descrito más arriba
y en el capítulo 7.1. La función
actionEntryCallback
debe ser aportada por el programador, y será ejecutada cuando
la acción a la que está asociada se active.
Añade una lista de entradas a un grupo de acción con:
actionGroupAddActions :: ActionGroup -> [ActionEntry] -> IO ()
Despés el grupo se inserta usando
uiManagerInsertActionGroup
como antes.
Hay funciones similares para RadioAction
y ToggleAction
.
Las acciones Radio (Radio actions) permiten al usuario seleccionar entre varias posibilidades,
de las que sólo una puede estar activa. Debido a esto tiene sentido definirlas todas juntas.
La definición es:
data RadioActionEntry = RadioActionEntry { radioActionName :: String radioActionLabel :: String radioActionStockId :: (Maybe String) radioActionAccelerator :: (Maybe String) radioActionTooltip :: (Maybe String) radioActionValue :: Int }
Los primeros 5 campos de nuevo se usan como se podría esperar. El
radioActionValue
(Valor de acción del radio) identifica cada una de las
posibles selecciones. La incorporación al grupo se realiza con:
actionGroupAddRadioActions :: ActionGroup -> [RadioActionEntry] -> Int -> (RadioAction -> IO ()) -> IO ()
El parámetro
Int
es el valor de la acción para ser activada inicialmente, o -1
si no va a ser ninguna.
Nota: En el siguiente ejemplo esto parece no tener efecto; la última acción está siempre seleccionada inicialmente.
La función de tipo
(RadioAction -> IO ())
se ejecuta siempre que esa acción
se activa.
Las acciones Toggle tienen un valor
Bool
y cada una puede establecerse o no. La
ToggleActionEntry
se define como:
data ToggleActionEntry = ToggleActionEntry { toggleActionName :: String toggleActionLabel :: String toggleActionStockId :: (Maybe String) toggleActionAccelerator :: (Maybe String) toggleActionTooltip :: (Maybe String) toggleActionCallback :: (IO ()) toggleActionIsActive :: Bool }
El ejemplo que tenemos a continuación demuestra el uso de acciones toggle así como acciones radio.
Nota: La función
toggleActionCallback
tiene el valor equivocado en
mi plataforma; el truco es, por supuesto, usar la función not
.
Los botones radio pueden controlar un modo "resaltado", como en el editor de texto gedit, del cual está copiado. El primer menú tiene un botón y dos submenús que contienen los items restantes. Además, uno de los botones radio es un elemento de un toolbar. Esta distribución está controlada completamente por la primera definición XML.
Las acciones toggle son elementos de otro menú, y dos de estos están también colocados en una barra de herramientas. Su distribución está determinada por la segunda definición XML.
Lo interesante es que el
uiManager
puede fusionar estas definiciones del ui, simplemente
añadiendolas, como se verá más adelante. Así que puedes definir tus menús en módulos separados
y combinarlos fácilmente más tarde en el módulo principal. De acuerdo a la documentación,
el gestor de ui (ui manager) es suficientemente inteligente en esto, y por supuesto
puedes usar nombres iguales en las definiciones XML que se diferencien en los caminos.
Pero recuerda que la
String
que denota una acción, debe ser única para cada acción.
También es posible separar los menús y los toolbars, usando las funciones
MergeId
y uiManagerRemoveUi
.
De este modo puedes gestionar menús y toolbars dinámicamente.
import Graphics.UI.Gtk main :: IO () main= do initGUI window <- windowNew set window [windowTitle := "Radio and Toggle Actions", windowDefaultWidth := 400, windowDefaultHeight := 200 ] mhma <- actionNew "MHMA" "Highlight\nMode" Nothing Nothing msma <- actionNew "MSMA" "Source" Nothing Nothing mmma <- actionNew "MMMA" "Markup" Nothing Nothing agr1 <- actionGroupNew "AGR1" mapM_ (actionGroupAddAction agr1) [mhma,msma,mmma] actionGroupAddRadioActions agr1 hlmods 0 myOnChange vima <- actionNew "VIMA" "View" Nothing Nothing agr2 <- actionGroupNew "AGR2" actionGroupAddAction agr2 vima actionGroupAddToggleActions agr2 togls uiman <- uiManagerNew uiManagerAddUiFromString uiman uiDef1 uiManagerInsertActionGroup uiman agr1 0 uiManagerAddUiFromString uiman uiDef2 uiManagerInsertActionGroup uiman agr2 1 mayMenubar <- uiManagerGetWidget uiman "/ui/menubar" let mb = case mayMenubar of (Just x) -> x Nothing -> error "Cannot get menu bar." mayToolbar <- uiManagerGetWidget uiman "/ui/toolbar" let tb = case mayToolbar of (Just x) -> x Nothing -> error "Cannot get tool bar." box <- vBoxNew False 0 containerAdd window box boxPackStart box mb PackNatural 0 boxPackStart box tb PackNatural 0 widgetShowAll window onDestroy window mainQuit mainGUI hlmods :: [RadioActionEntry] hlmods = [ RadioActionEntry "NOA" "None" Nothing Nothing Nothing 0, RadioActionEntry "SHA" "Haskell" (Just stockHome) Nothing Nothing 1, RadioActionEntry "SCA" "C" Nothing Nothing Nothing 2, RadioActionEntry "SJA" "Java" Nothing Nothing Nothing 3, RadioActionEntry "MHA" "HTML" Nothing Nothing Nothing 4, RadioActionEntry "MXA" "XML" Nothing Nothing Nothing 5] myOnChange :: RadioAction -> IO () myOnChange ra = do val <- radioActionGetCurrentValue ra putStrLn ("RadioAction " ++ (show val) ++ " now active.") uiDef1 = " <ui> \ \ <menubar>\ \ <menu action=\"MHMA\">\ \ <menuitem action=\"NOA\" />\ \ <separator />\ \ <menu action=\"MSMA\">\ \ <menuitem action= \"SHA\" /> \ \ <menuitem action= \"SCA\" /> \ \ <menuitem action= \"SJA\" /> \ \ </menu>\ \ <menu action=\"MMMA\">\ \ <menuitem action= \"MHA\" /> \ \ <menuitem action= \"MXA\" /> \ \ </menu>\ \ </menu>\ \ </menubar>\ \ <toolbar>\ \ <toolitem action=\"SHA\" />\ \ </toolbar>\ \ </ui> " togls :: [ToggleActionEntry] togls = let mste = ToggleActionEntry "MST" "Messages" Nothing Nothing Nothing (myTog mste) False ttte = ToggleActionEntry "ATT" "Attributes" Nothing Nothing Nothing (myTog ttte) False erte = ToggleActionEntry "ERT" "Errors" (Just stockInfo) Nothing Nothing (myTog erte) True in [mste,ttte,erte] myTog :: ToggleActionEntry -> IO () myTog te = putStrLn ("The state of " ++ (toggleActionName te) ++ " (" ++ (toggleActionLabel te) ++ ") " ++ " is now " ++ (show $ not (toggleActionIsActive te))) uiDef2 = "<ui>\ \ <menubar>\ \ <menu action=\"VIMA\">\ \ <menuitem action=\"MST\" />\ \ <menuitem action=\"ATT\" />\ \ <menuitem action=\"ERT\" />\ \ </menu>\ \ </menubar>\ \ <toolbar>\ \ <toolitem action=\"MST\" />\ \ <toolitem action=\"ERT\" />\ \ </toolbar>\ \ </ui>"