module Sifflet.UI.Callback ( CBMgr, CBMgrCmd(..), mkCBMgr , MenuSpec(..), MenuItemSpec(..), MenuItemAction , createMenuBar, addMenu, createMenu, createMenuItem , modifyIORefIO ) where import Data.IORef import Graphics.UI.Gtk import Sifflet.UI.Types -- | The CBMgr (Callback manager) encapsulates (in an enclosure, no less!) -- an IORef VPUI. It is used *solely* to set up callbacks -- and similar stuff in Gtk, where the callback needs access -- to the IORef. By passing a CBMgr to a function, we can -- avoid passing the IORef directly, and all the harm and -- confusion that could result. -- -- We only need *one* CBMgr for the application; -- however, two CBMgrs with the same IORef are logically equivalent, -- so there would be no harm in having two as long as they share one IORef. type CBMgr = CBMgrCmd -> IO () -- | Commands for the CBMgr data CBMgrCmd = -- window events OnWindowConfigure Window (IORef VPUI -> EventM EConfigure Bool) | OnWindowDestroy Window (IORef VPUI -> IO ()) | OnWindowKeyPress Window (IORef VPUI -> EventM EKey Bool) -- layout events | OnLayoutExpose Layout (IORef VPUI -> EventM EExpose Bool) | OnLayoutMouseMove Layout (IORef VPUI -> EventM EMotion Bool) | OnLayoutButtonPress Layout (IORef VPUI -> EventM EButton Bool) | OnLayoutButtonRelease Layout (IORef VPUI -> EventM EButton Bool) -- other events | OnMenuItemActivateLeaf MenuItem (VPUI -> IO VPUI) | OnEntryActivate Entry (IORef VPUI -> IO ()) | AfterButtonClicked Button (IORef VPUI -> IO ()) | UMTest -- | Create the CBMgr mkCBMgr :: IORef VPUI -> CBMgr mkCBMgr uiref cmd = case cmd of -- window events OnWindowConfigure window action -> on window configureEvent (action uiref) >> return () OnWindowDestroy window action -> onDestroy window (action uiref) >> return () OnWindowKeyPress window action -> on window keyPressEvent (action uiref) >> return () -- layout events OnLayoutExpose layout action -> on layout exposeEvent (action uiref) >> return () OnLayoutMouseMove layout action -> on layout motionNotifyEvent (action uiref) >> return () OnLayoutButtonPress layout action -> on layout buttonPressEvent (action uiref) >> return () OnLayoutButtonRelease layout action -> on layout buttonReleaseEvent (action uiref) >> return () -- other events OnMenuItemActivateLeaf menuItem action -> onActivateLeaf menuItem (modifyIORefIO uiref action) >> return () OnEntryActivate entry action -> onEntryActivate entry (action uiref) >> return () AfterButtonClicked button action -> afterClicked button (action uiref) >> return () UMTest -> putStrLn "UMTest" -- ============================================================ -- MENUS -- Easy creation of menus from lists. -- Originally from ~/src/haskell-etudes/gtk2hs/gMenu.hs data MenuSpec = MenuSpec String [MenuItemSpec] data MenuItemSpec = MenuItem String MenuItemAction | SubMenu MenuSpec type MenuItemAction = VPUI -> IO VPUI -- was just IO () createMenuBar :: [MenuSpec] -> CBMgr -> IO MenuBar createMenuBar menuSpecs cbmgr = do bar <- menuBarNew mapM_ (addMenu bar cbmgr) menuSpecs return bar addMenu :: MenuBar -> CBMgr -> MenuSpec -> IO () addMenu mbar cbmgr mspec@(MenuSpec name _itemSpecs) = do menuHead <- menuItemNewWithLabel name -- visible "item" at top of the menu menuShellAppend mbar menuHead -- Right-justify help menu. -- Deprecated (bad for right-to-left languages), -- but retained for compatibility with menus_hard.py. menuItemSetRightJustified menuHead (name == "Help") -- ?????? -- menu = the container for menu items menu <- createMenu mspec cbmgr menuItemSetSubmenu menuHead menu createMenu :: MenuSpec -> CBMgr -> IO Menu createMenu (MenuSpec _name itemSpecs) cbmgr = do menu <- menuNew mapM_ (createMenuItem menu cbmgr) itemSpecs return menu createMenuItem :: Menu -> CBMgr -> MenuItemSpec -> IO () createMenuItem menu cbmgr mispec = case mispec of MenuItem label action -> do { item <- menuItemNewWithLabel label ; cbmgr (OnMenuItemActivateLeaf item action) -- may need to read/write IORef here *** ; menuShellAppend menu item } SubMenu subspec@(MenuSpec label _itemSpecs) -> do { item <- menuItemNewWithLabel label ; submenu <- createMenu subspec cbmgr ; menuItemSetSubmenu item submenu ; menuShellAppend menu item } -- | Read an IORef, update with IO, and write the updated value. -- This is like modifyIORef, but the type of the second argument is (a -> IO a) -- instead of (a -> a). modifyIORefIO :: IORef a -> (a -> IO a) -> IO () modifyIORefIO ref updateIO = readIORef ref >>= updateIO >>= writeIORef ref