{-# OPTIONS_HADDOCK hide #-}
module RPN (
pushWidget, touchWidget, pullWidget, RPN (RPN), io2rpn,
widgetsFromRpn
) where
import Graphics.UI.Gtk (Widget,ContainerClass)
import Data.Monoid
import Control.Monad.State
import Data.Maybe
import Data.List
import CanBeCasted
-- | All operators in our reverse polish notation implementation
-- are going to operate on a stack, represented by a
-- @['Graphics.UI.Gtk.Widget']@. 'ActionType' is the type
-- of such operations. A few functions are also defined to
-- manipulate that stack.
type ActionType a = StateT [Widget] IO a
-- | 'pushWidget' inserts a widget in the stack, casting it
-- to Widget.
pushWidget :: ( CanBeCasted w ) => w -> ActionType ()
pushWidget w = case (glibCast w) of
Just w' -> modify (w':)
_ -> return ()
-- | 'touchWidget' applies a @(a -> IO ())@ to the last
-- inserted widget in the stack that can be casted to @a@,
-- if there is one. The widget remains in the stack.
touchWidget :: (CanBeCasted a) => (a -> IO ()) -> ActionType ()
touchWidget f = liftM (listToMaybe . mapMaybe glibCast) get >>=
maybe (return ()) (liftIO . f)
-- | 'pullWidget' will apply a packing function (like, say,
-- 'Graphics.UI.Gtk.containerAdd') to the last container
-- inserted in the widget list that can be casted to type @a@
-- and the last inserted widget that is not such container.
-- The container remains in the list.
pullWidget :: (CanBeCasted a, ContainerClass a) =>
(a -> Widget -> IO ()) -> ActionType ()
pullWidget f = do
casted <- liftM (map glibCast) get
let iC = take 1 $ findIndices isJust casted
let iW = [0..length casted - 1] \\ iC
case (listToMaybe iC,listToMaybe iW) of
(Just ic, Just iw) -> do
let Just c = casted !! ic
w <- liftM (!! iw) get
liftIO $ f c w
modify $ (\(a,b) -> a ++ drop 1 b) . splitAt iw
_ -> return ()
-- | 'RPN' is the type of all operators, and encapsulates the
-- underline machinery. It instantiates 'Data.Monoid.Monoid',
-- therefore we can sequence operators in a list, and also
-- create new operators by 'Data.Monoid.mconcat'ing others.
newtype RPN = RPN ( ActionType () )
instance Monoid RPN where
mempty = RPN $ return ()
mappend (RPN a) (RPN b) = RPN (a >> b)
-- | Using 'io2rpn' we can make an @IO widget@ into an operator
-- that inserts a widget in the stack.
io2rpn :: (CanBeCasted w) => IO w -> RPN
io2rpn = RPN . (>>= pushWidget) . liftIO
-- | After we describe our widgets using a list of 'RPN's,
-- we use 'widgetsFromRpn' to get them.
widgetsFromRpn :: [RPN] -> IO [Widget]
widgetsFromRpn list = liftM reverse $ execStateT unified []
where RPN unified = mconcat list