Generic display functions for abstract widgets

\begin{code}
module DisplaySimple where
import WH
import Widget
import PileArray
import IOExts
import FranCore
import Identify
import Monad
import FM
import MapG
import Conf
import GUIDef
\end{code}

We use the DisplayInfo type to pass actions down to all child widgets.
Widgets may be visible at any given time; will be deleted on a given event;
appear in some form of pile (see PileArray.lhs); may have listeners to their
user input; we also have a unique name supply for use.

\begin{code}
data DisplayInfo = DisplayInfo {visible :: ! BoolB,
			        dieE :: ! (Event Bool),
				pArray :: ! PileArray,
				bindEvents :: [Bind],
				nameRef :: IORef Int,
				style :: Conf Style,
				guiDef_ :: GUIDef}

data Bind = Bind (forall w . Bindable w => w -> IO (IO ()))

newName :: IORef Int -> IO Int
newName ref = do x<- readIORef ref;writeIORef ref (x+1);return x

initDisplayInfo :: GUIDef -> Event () -> IO DisplayInfo
initDisplayInfo = initDisplayInfo' emptyConf

initDisplayInfo' :: Conf Style -> GUIDef -> Event () -> IO DisplayInfo
initDisplayInfo' s guiDef die  = do
 parr <- pileArray 30
 ref <- newIORef 0
 return $! DisplayInfo trueB (die -=> True) parr [] ref s guiDef
\end{code}

Display an individual item, given the item; a function to convert boolean
visibility into display actions; a hide action to perform when the widget 
is deleted (if it is currently visible; and a delete action to perform 
when the widget dies. Along with the Display info for the widget.

\begin{code}
displayItem :: (Bindable w,WidgetItem w) => w -> (BoolB -> IOB ()) 
	    -> IO () -> IO () 
	    -> DisplayInfo
            -> IO ()
displayItem w dispB hide del (DisplayInfo {visible = vis,dieE = dieE,
				           bindEvents = es,
 					   guiDef_ = guiDef}) = do 
  rm <- addIOB (dispB vis)
  rms <- mapM (\(Bind e) -> e w) es
  rml <- addListener (onceE' dieE)
		    (snapshotL_ vis $ mkL $ \b -> do 
				rm;sequence_ rms;del;when b $ hide)
  addFinaliserW w (do rm;sequence_ rms;rml)
  return ()
\end{code}

display a widget in a pile array. We need to generate a unique pile array
name for it; insert it into the pile (associating any groups there may
be with it; widgets are initially hidden. When we show and hide the widget
we must update the pile array.

\begin{code}
displayPAItem :: (Bindable w,WidgetItem w) => 
	       PileArray -- the pile the widget is in
	    -> w  -- the widget
	    -> ((Bool -> b -> IO ()) -> BoolB -> IOB ()) 
                    -- how to handle showing and hiding the widget 
	    -> (EltName -> b -> IO ())
                    -- show the widget (with some abstract type specific data)
	    -> (IO ())
                    -- hide the widget
	    -> DisplayInfo -- the display info
	    -> [GroupRef] -- the groups the widget is in
            -> IO EltName

displayPAItem pa w mkb showIt hideIt di gps = do
  elt <- getEltName pa
  insertPileElt pa (uniqueId w) elt Nothing gps
  hidePileElt pa elt
  let disp True ps = do
	showPileElt pa elt
	showIt elt ps 
      disp False _ = do
	hidePileElt pa elt
	hideIt
  displayItem w (mkb disp) hideIt (deletePileElt pa elt) 
		di
  return elt
\end{code}

find the element below the widget in the given pile

\begin{code}
findEltAbove :: PileArray -> EltName -> IO (Maybe EltData)
findEltAbove pa elt = do
	mbd <- getEltData pa elt
	mbe <- maybe (return Nothing)
		       (maybe (return Nothing)
			      (getEltData pa)
			 . visBelowElt) mbd
        return mbe
\end{code}

display an abstract widget, given a function to display an item;
a function to make a group for moving items in the stacking order;
the DisplayInfo and some type specific display info.

\begin{code}
displayW :: (DisplayInfo -> b -> a -> IO ())
	 -> (DisplayInfo -> b -> IO (MkMoveOp b)) 
         -> DisplayInfo -> b 
         -> GWidgetB a 
         -> IO ()
displayW disp gp info sinfo EmptyW = return ()

-- compose the two widgets displaying the widget at the back first
displayW disp gp info sinfo (Compose w1 w2) = do
 displayW disp gp info sinfo w2
 displayW disp gp info sinfo w1

displayW disp gp info sinfo (GWidgetB w) = disp info sinfo w

-- when we have an ifB, display the first widget when true, and the second
-- when False
displayW disp gp info@(DisplayInfo {visible = vis}) sinfo (CondWUnopt b w1 w2)
 =
 do displayW disp gp (info {visible = vis &&* b}) sinfo w1
    displayW disp gp (info {visible = vis &&* notB b}) sinfo w2


-- handle style
displayW disp gp info@(DisplayInfo {style =oldcs,visible=vis}) sinfo (WithStyle cs w) =
 displayW disp gp (info {style = composeConfs $ cs ++ [oldcs]}) sinfo w


-- bind user input by adding a listener to those for all widgets
displayW disp gp info@(DisplayInfo {bindEvents = es}) sinfo (GrabInput a l w)
 =
 displayW disp gp (info{bindEvents = (Bind (\w -> bind w a l)):es}) sinfo w

-- display a pile of widgets
displayW (disp :: (DisplayInfo -> b -> a -> IO ())) gp info sinfo 
         (PileW (APile init alter)) = do
 mkmoveop <- gp info sinfo -- make a group for moving stacking order
 is <- init -- get initial pile
 rm <- displayPile is alter mkmoveop -- display the pile
 addListener (onceE' $ dieE info) (mkL_ rm) -- stop when deleted
 return ()
   where
   displayPile :: [(Ident,GWidgetB a)] -> Event (PileOp (GWidgetB a))
	       -> (DisplayInfo,b,MkMoveOp' b) -> IO Remover
   displayPile is alter (di,pi,mkmoveop) = do
      displayList is 
      rm <- addListener alter (mkL dispE)
      addListener (onceE' $ dieE di) $ mkL_ rm
    where
     -- display a list of items
     displayList :: [(Ident,GWidgetB a)] -> IO ()
     displayList as =
             mapM_ (\(n,a) -> displayOne n a PlaceTop)
		   (reverse as)

     dispE :: PileOp (GWidgetB a) -> IO ()
     dispE (InsertElt a id pos) = 
       displayOne id a pos
     dispE (ResetElts rs) =
       displayList rs
     dispE _ = return ()

     isDelete :: Ident -> PileOp (GWidgetB a) -> Maybe Bool
     isDelete n (DeleteElt n1) | n == n1 = Just True
     isDelete n (ResetElts _) = Just True
     isDelete n _ = Nothing

     -- display an item. It should be deleted on a delete event,
     -- (ie on reset or delete that element)

     displayOne :: Ident -> GWidgetB a -> PlacePos Ident -> IO ()
     displayOne id a pos = do
      let dieE' = dieE info .|. mapMaybeE  (isDelete id) alter

      -- turn the widget into a pile for group restacking
      (moveop,info,sinfo) <- mkmoveop (di {dieE = dieE'}) pi id pos 

      displayW disp gp info sinfo a
      rm <- raiseIt id moveop 
      addListener (onceE' dieE') $ mkL_ rm
      return ()

     -- when we hear move ops restack the widget.
     raiseIt :: Ident -> MoveOp -> IO Remover
     raiseIt id moveop = do
      addListener  alter  (mapMaybeL (raiser id) moveop)
      
     raiser :: Ident -> PileOp (GWidgetB a) -> Maybe (PlacePos Ident)
     raiser id (MoveElt id' pos) | id == id' = Just pos
     raiser _ _ = Nothing
\end{code}

Make a group to allow restacking. We first make a generic group, with an 
operation to make individual stacking groups (we do this for a whole pile).

We then make specific groups with a name and an initial position. This 
returns a listener that hears about restacking of that group.

\begin{code}
type MkMoveOp b = (DisplayInfo,b,MkMoveOp' b)

type MkMoveOp' b = DisplayInfo -> b -> Ident -> PlacePos Ident
		 -> IO (MoveOp,DisplayInfo,b)
type MoveOp = Listener (PlacePos Ident)
\end{code}

To make a mover group.
\begin{code}
mkMover :: PileArray -- the pile
        -> IORef (FiniteMap Ident c) 
             -- a map to lookup object names and get back a value to refer to
	     -- when restacking 
        -> Ident -- the identifier of this object
        -> c -- the restacking identifier of this object
	-> (PileArray -> c -> Maybe c -> IO d) 
             -- raise an element above another
	-> (PileArray -> c -> Maybe c -> IO d)
             -- lower an element below another
	-> Event Bool -- when to stop
	-> IO (PlacePos Ident -> IO ())
mkMover pa fmv ident elt raisePileElt lowerPileElt dieE = do
 let findEltinTbl v n = do fm <- readIORef v;return $ lookupFM fm n
 updIORef fmv $ \fm -> addToFM fm ident elt
 addListener (onceE' dieE) $ mkL_ $updIORef fmv (\fm -> delFromFM fm ident)
 let moveInPile pos = do
           posg <- mapG (findEltinTbl fmv) pos
           case posg of
            PlaceBefore g -> raisePileElt pa elt g
            PlaceAfter g -> lowerPileElt pa elt g
            PlaceTop -> raisePileElt pa elt Nothing
            PlaceBottom -> lowerPileElt pa elt Nothing
	   return ()
 return (moveInPile)
\end{code}
