packages feed

AutoForms-0.4.0: src/Graphics/UI/AF/WxForm/WxM.hs

{-# LANGUAGE ExistentialQuantification, GeneralizedNewtypeDeriving, RankNTypes #-}

module Graphics.UI.AF.WxForm.WxM
    ( io, Widget(..), toWidget, Parent(Parent)
    , WxAct
    , startI
    , getParentI
    --
    , WxM, runWxM, wxActToListenerM, liftWxActToListenerM
    , getFrame, getPanel, withPanel
    , liftIO, liftIO'
    , withMaxDepth, getMaxDepth, goDeeper
    , extractGui, comIOAddGui, getPostponedIO, postponeAction', getClose, getCloseWindow
    , WxMGUI(..)
    )

where

import qualified Graphics.UI.AF.General.AutoForm as AF

import Control.Monad.State
import Control.Monad.Reader
import Control.Monad.Writer
import Control.Monad.Fix
import Control.Monad.Trans(MonadIO, liftIO)

import qualified Graphics.UI.WX      as Wx
import qualified Graphics.UI.WXCore  as Wx

import Graphics.UI.AF.WxForm.ComIO
import Graphics.UI.AF.WxForm.GUI

data Widget = forall w. Widget (Wx.Window w)

toWidget :: forall w. Wx.Window w
         -> Widget
toWidget w = Widget w

data Parent = forall w. Parent (Wx.Window w)

instance AF.SimpleDialog WxM where
    infoDialog title msg  = standardDialog' (\w -> Wx.infoDialog w title msg)
    errorDialog title msg = standardDialog' (\w -> Wx.errorDialog w title msg)

standardDialog' :: (forall w. Wx.Window w -> IO a) -> WxM a
standardDialog' dialog =
    do Parent parent <- getFrame
       x <- liftIO $ dialog parent
       return x

instance AF.SimpleDialog WxAct where
    infoDialog title msg  = standardDialog'' (\w -> Wx.infoDialog w title msg)
    errorDialog title msg = standardDialog'' (\w -> Wx.errorDialog w title msg)

standardDialog'' :: (forall w. Wx.Window w -> IO a) -> WxAct a
standardDialog'' dialog =
    do (Parent parent) <- getParentI
       x <- liftIO $ dialog parent
       return x

-- (Parent, closeWindow)
newtype WxAct a = WxAct { wxAct' :: ReaderT (Parent, IO()) ListenerM a }
    deriving ( Monad, MonadIO, MonadListener, Observable (ComIO b)
             , Observable OnChangeVars
             )

liftWxActToListenerM :: ((forall b. WxAct b -> ListenerM b) -> IO a) -> WxM a
liftWxActToListenerM f =
    do p        <- getFrame
       close    <- getClose
       let local' act = runReaderT (wxAct' act) (p, close)
       liftIO $ f local'


getParentI :: WxAct Parent
getParentI = WxAct $ asks fst

getCloseWindow :: WxAct (IO ())
getCloseWindow = WxAct $ liftM snd ask

wxActToListenerM :: WxAct a -> WxM (ListenerM a)
wxActToListenerM act = 
    do p      <- getFrame
       close  <- getClose
       return (runReaderT (wxAct' act) (p, close))

---------------------------- WxM ----------------------------------
newtype WxM a = WxM { wxm' :: StateT WxMState (ReaderT WxMReader IO) a }
    deriving (Monad, MonadIO, MonadFix)

data WxMReader = WxMReader { frame'    :: Parent     -- ^The parent frame or dialog.
                           , close'    :: IO ()      -- ^Closing the parent frame or dialog.
                           , panel'    :: Parent     -- ^The panel or frame the widgets should
                                                     -- be attached to, like `textCtrl panel []`.
                           , maxDepth' :: Int
                           }

data WxMGUI = WxMGUI { wxmWidget :: Widget, wxmGui :: GUI }

data WxMState = WxMState { wxmGuis                :: [WxMGUI]
                         , wxmAfterCreationAction :: WxAct()
                         , wxmUpdateEnabledness   :: IO ()
                         , wxmGlobalEnabled       :: Wx.Var Bool
                         }
initialWxMState :: IO WxMState
initialWxMState = do globalEnable <- Wx.varCreate True
                     return $ WxMState [] (return()) (return ()) globalEnable

extractGui :: WxM (ComIO a) -> WxM (ComIO a, Widget, GUI)
extractGui wxmComIO
    = testLiftWxM2WxM (wxmComIO >>= getGui)
    where
      testLiftWxM2WxM :: WxM a -> WxM a
      testLiftWxM2WxM wxm = 
          do iState <- io $ initialWxMState
             (x, st) <- WxM $ lift $ runStateT (wxm' wxm) iState
             WxM $ modify (\y -> y { wxmAfterCreationAction
                                         = do wxmAfterCreationAction y
                                              wxmAfterCreationAction st
                                   }
                          )
             return x
      -- FIXME: no label
      getGui :: ComIO a -> WxM (ComIO a, Widget, GUI)
      getGui comIO
          = do -- We handle all the encapsulated guis here, as enabledness should apply to them all.
               -- And we _don't_ do listeners, getValue, setValue as they shold only apply to
               -- the input ComIO (see argument wxmComIO).
               WxMState { wxmGuis              = guis
                        , wxmUpdateEnabledness = updateEnabledness
                        , wxmGlobalEnabled     = globalEnabled
                        } <- WxM $ get
               let com' = comIO { pickSetEnabled = \enabled -> do Wx.varSet globalEnabled enabled
                                                                  updateEnabledness
                                }
               case guis of
                 []    -> error "Cannot handle yet :("
                 [gui] -> return (com', wxmWidget gui, wxmGui gui)
                 guis' -> return ( com', (wxmWidget $ head guis')
                                 , containerGUI (map wxmGui guis') (PriLabel BadConstr "")
                                 )

comIOAddGui :: AddGui WxM ComIO a
comIOAddGui w gui comIO =
    do guis <- WxM $ gets wxmGuis
       case (reverse guis, gui) of
         ((WxMGUI wid (Buttons lay)):xs, Buttons lay')
             -> setGuis $ reverse $ (WxMGUI wid (Buttons $ Wx.row 5 [lay, lay'])):xs  -- FIXME: merge comio
         _   -> setGuis (guis ++ [WxMGUI (toWidget w) gui])
       --
       enabledVar    <- io $ Wx.varCreate True
       globalEnabled <- WxM $ gets wxmGlobalEnabled
       let updateEnabledness = do enabled <- liftM2 (&&) (Wx.varGet enabledVar) (Wx.varGet globalEnabled)
                                  pickSetEnabled comIO enabled
       WxM $ modify (\x -> x { wxmUpdateEnabledness = 
                                   do wxmUpdateEnabledness x
                                      updateEnabledness
                             }
                    )
       return $ comIO { pickSetEnabled = \enable -> do Wx.varSet enabledVar enable
                                                       updateEnabledness
                      }

defaultMaxDepth :: Int
defaultMaxDepth = 5


startI :: String -> WxM a -> IO ()
startI title wxq
    = Wx.start $ do f <- Wx.frame [ Wx.text Wx.:= title ]
                    p <- Wx.panel f []
                    (guis, afterCreationAction, _) <- execWxM wxq (Parent f) (Wx.close f) p
                    (lay, _) <- unboxedLayout $ containerGUI (map wxmGui guis) labelless
                    unsafeRunListenerM (runReaderT (wxAct' afterCreationAction) (Parent f, Wx.close f))
                    Wx.set f [ Wx.layout Wx.:= Wx.container p $ Wx.fill lay ]

-- newtype WxM a = WxM { wxm' :: StateT WxMState (ReaderT WxMReader IO) a }
execWxM :: WxM a -> Parent -> IO() -> Wx.Window w -> IO ([WxMGUI], WxAct(), a)
execWxM wxm parentWindow c panel =
    do iState <- initialWxMState
       (val, st) <- runReaderT (runStateT (wxm' wxm) iState) (WxMReader parentWindow c (Parent panel) defaultMaxDepth)
       return (wxmGuis st, wxmAfterCreationAction st, val)

runWxM :: WxM a -> Wx.Window w' -> IO() -> Wx.Window w -> IO a
runWxM wxm parentWindow c panel =
    do iState <- initialWxMState
       runReaderT (evalStateT (wxm' wxm) iState) (WxMReader (Parent parentWindow) c (Parent panel) defaultMaxDepth)

liftIO' :: ((forall b. WxM b -> IO b) -> IO a) -> WxM a
liftIO' f = do Parent parentWindow <- getFrame
               c                   <- getClose
               Parent panel        <- getPanel
               let local' wxm = runWxM wxm parentWindow c panel
               liftIO $ f local'

setGuis :: [WxMGUI] -> WxM ()
setGuis guis = WxM $ modify (\state -> state {wxmGuis = guis} )

getPostponedIO :: WxM (WxAct ())
getPostponedIO = WxM $ gets wxmAfterCreationAction

postponeAction' :: WxAct a -> WxM ()
postponeAction' action = WxM $ modify addAction where
    addAction state = state { wxmAfterCreationAction = wxmAfterCreationAction state >> (action >> return ()) }

-- |Returns enclosing frame or dialog.
getFrame :: WxM Parent
getFrame = WxM $ ask >>= return . frame'

-- |Returns an action to close the enclosing frame or dialog.
getClose :: WxM (IO())
getClose = WxM $ ask >>= return . close'

-- |Returns the panel or frame the widgets should be attached to, like `textCtrl panel []`.
getPanel :: WxM Parent
getPanel = WxM $ ask >>= return . panel'

-- |Sets the panel or frame the widgets should be attached to, like `textCtrl panel []`.
withPanel :: Wx.Window w -> WxM a -> WxM a
withPanel panel wxm = WxM $ local (\x -> x { panel' = Parent panel }) $ wxm' wxm

getMaxDepth :: WxM Int
getMaxDepth = WxM $ ask >>= return . maxDepth'

withMaxDepth :: (Int -> Int) -> WxM a -> WxM a
withMaxDepth maxDepth wxm =
    do currentMaxD <- getMaxDepth
       WxM $ local (\x -> x { maxDepth' = maxDepth currentMaxD }) $ wxm' wxm

goDeeper :: WxM Bool
goDeeper = do d <- getMaxDepth
              return (d > 0)