Franttk primitive widgets, these have added functionality on top of the underlying toolkit widgets. \begin{code} module PrimWidget (module PrimWidget,PrimWidget)where import GUI import IOExts import Weak import Compatibility import MutSet import Maybe import Monad import WH import FRPWrap hiding (addListener) import WidgetExports import FranPrim (debugMsg,setFranTime,runFranOnce,wrapIOL,Event,Listener, addListener,tellL,neverE,primNewWire,Wire(..)) import Conf \end{code} Recall from GUIDef.lhs the definition of PrimWidget and friends type RemRef = IORef (Maybe Remover) type Window = PrimWidget WindowPrim type Canvas = PrimWidget CanvasPrim A primwidget has an addingRef used in primAdd to control redisplaying of widgets (see primAdd), access to the GUIDef state from when it was create, the widget itself, an action to hide the widget, a weak reference to the hide action, and an event that listens to all command actions on the widget. data PrimWidget w = PrimWidget { addingRef :: IORef Int, guiDef :: ! GUIDef, widgetPrim :: ! w, hideRef :: ! RemRef, hideRefWP :: ! (Weak RemRef), actionPE :: Event () } making a prim widget. We get the current gui def. We create the widget using the current window. We make a reference to hold the hide action for the widget. We then create a weak reference to this hide ref, with a finaliser that destroys the widget. Recall that in GUIDef there is a mutable list of visible items. When a widget is visible, then its hide ref goes in this list. Therefore this ref cell remains live until the widget becomes invisible and there are no other references to it that could make it visible again. At this point we can safely destroy the widget. \begin{code} mkPrimWidget' :: WidgetItem w => (WindowPrim -> [Config] -> GUIInfo -> IO w) -> [ConfigName] -> [Conf (PrimWidget w)] -> (PrimWidget w -> GUI (PrimWidget w)) -> GUI (PrimWidget w) mkPrimWidget' mk valids cs alter = do info <- getGUIDef s <- getStyle (act,prim) <- liftIO $ do aref <- newIORef 0 case getConfigs valids s cs of (cs,as) -> do w <- mk (widgetPrim $ currentWindow info) cs (guiInfo info) ref <- newIORef Nothing wp <- mkWeakIORef ref (debugMsg ("kill",uniqueId w) >> destroy w) return $! (as,PrimWidget aref info w ref wp neverE) -- this alter action may change the action listener event to be a real -- one if so, then the act function will probably attempt to add action -- listeners so order of these two operations matters prim <- alter prim act prim return prim mkPrimWidget :: WidgetItem w => (WindowPrim -> [Config] -> GUIInfo -> IO w) -> [ConfigName] -> [Conf (PrimWidget w)] -> GUI (PrimWidget w) mkPrimWidget mk valids cs = mkPrimWidget' mk valids cs (return . id) \end{code} make a primitive widget that can have an action listener, so make an event that connects to the action listener \begin{code} mkPrimWidgetAct :: (Has_Action w,WidgetItem w) => (WindowPrim -> [Config] -> GUIInfo -> IO w) -> [ConfigName] -> [Conf (PrimWidget w)] -> GUI (PrimWidget w) mkPrimWidgetAct mk valids cs = mkPrimWidget' mk valids cs $ \prim -> do ev <- actionE (widgetPrim prim) return $ prim {actionPE = ev} \end{code} action listeners on primwidgets are all merged to connect to a single event, that listens to actions on the widget. This allows more than one action listener to be added at a time. We make a primitive repeater with subscribe and unsubscribe actions. If the event has no listeners and we add one, we'll add an actual listener to the widget, when we remove all the listeners from the event we also remove the listener on the widget. This reduces unnecessary work being done when nothing is interested in the widget. It is assumed that the underlying toolkit can handle multiple bindings to other sorts of events. Why? Because there are so many it would be inefficient to use this technique, and tcl-tk is able to do that. Ok so it may be a bad assumption but we'll see... \begin{code} actionE :: Has_Action w => w -> GUI (Event ()) actionE w = primMkEvent w (\l -> action w $ tellL l ()) primMkEvent :: WidgetItem w => w -> (Listener a -> IO (IO ())) -> GUI (Event a) primMkEvent w act = withGUIDef $ \g -> do fmap wireEvent $ fixIO $ \ ~(Wire l _) -> do rmver <- newIORef (return ()) let final = return () subscribe = do act (wrapIOL (primAction g) l) >>= writeIORef rmver unsubscribe = do readIORef rmver >>= id writeIORef rmver (return ()) primNewWire final subscribe unsubscribe \end{code} showing and hiding prim widgets. When we show the widget we make it active by adding its remover ref to the active widget set. We then stick the remove action for the set in the remover ref. When we make the widget inactive we can then just run the remover ref. \begin{code} -- the Bool says did we need to remap the widget mkActiveWidget :: GUIDef -> RemRef -> IO Bool mkActiveWidget guiDef ref = do mb <- readIORef ref if (isJust mb) then return False else do rm <- addToMutSet (activeWidgets guiDef) ref writeIORef ref (Just $ rm) return True -- the bool says did we need to unmap the widget hideActiveWidget :: RemRef -> IO Bool hideActiveWidget ref = do mb <- readIORef ref case mb of Nothing -> return False Just rm -> do {rm;return True} -- convenience function to apply previous two functions to a prim widget -- (used in ComponentWidgets.lhs) setActiveWidget :: PrimWidget w -> Bool -> IO () setActiveWidget (PrimWidget {guiDef = d,hideRef = ref}) b = if b then mkActiveWidget d ref >> return () else hideActiveWidget ref >> return () \end{code} We need to be careful when hiding and showing widgets. Why? With a panel widget it is possible to add a panel item to two different items. Adding it to a second box makes it vanish from the first. If we try to run the hide action for the first box, its no longer there. It may, however, still be in the second box. So removing it from the active list would be disastrous. We therefore keep a reference to whether it was this action that added. Only the action that added is allowed to remove. \begin{code} primAdd :: (w -> IO Remover) -> PrimWidget w -> IO Remover primAdd add (PrimWidget {addingRef = aref,guiDef = inf, widgetPrim = w,hideRef = ref,hideRefWP = wp}) = do v <- mkActiveWidget inf ref i <- readIORef aref let myi = i + 1 writeIORef aref myi rm <- add w mkRemover $ do n <- readIORef aref when (n == myi) $ do refMb <- deRefWeak wp v <- maybe (return False) hideActiveWidget refMb when v $ rm \end{code} begin{code} -- tried reference counting but it seems broken. primAdd :: (w -> IO Remover) -> PrimWidget w -> IO Remover primAdd add (PrimWidget {addingRef = aref,guiDef = inf, widgetPrim = w,hideRef = ref,hideRefWP = wp}) = do v <- mkActiveWidget inf ref i <- readIORef aref writeIORef aref (i + 1) rm <- add w mkRemover $ do n <- readIORef aref if n > 1 then writeIORef aref (n - 1) else do refMb <- deRefWeak wp v <- maybe (return False) hideActiveWidget refMb when v $ rm end{code} Instances for prim widgets \begin{code} --widget item. when we destroy the widget, first remove it from the --active set. instance WidgetItem w => WidgetItem (PrimWidget w) where cset (PrimWidget {widgetPrim = w}) = cset w destroy (PrimWidget {widgetPrim = w,hideRef = ref}) = do readIORef ref >>= maybe (return ()) id destroy w uniqueId (PrimWidget {widgetPrim = w}) = uniqueId w addFinaliserW (PrimWidget {widgetPrim = w}) = addFinaliserW w -- see primAdd for more on special add instance PanelItem w => PanelItem (PrimWidget w) where boxAdd w bx pm ps = primAdd (\w -> boxAdd w bx pm ps) w gridAdd w bx pm ps = primAdd (\w -> gridAdd w bx pm ps) w raise (PrimWidget {widgetPrim = w}) = raise w lower (PrimWidget {widgetPrim = w}) = lower w -- when we show a window item, make it active. when we unmap it make it -- inactive. instance WindowItem w => WindowItem (PrimWidget w) where showWindow (PrimWidget {guiDef = inf,widgetPrim = w,hideRef = ref}) = do v <- mkActiveWidget inf ref when v $ showWindow w hideWindow (PrimWidget {widgetPrim = w,hideRef = ref}) = do v <- hideActiveWidget ref when v $ hideWindow w iconifyWindow (PrimWidget {widgetPrim = w}) = iconifyWindow w getWinPanel (PrimWidget {widgetPrim = w}) = getWinPanel w -- bind user input to widgets. Note that we make the input a primAction, -- meaning that after the action happens we have to update behaviors, -- by running the fran workpool. see GUI.lhs FranPrim.hs and WorkPool.hs instance Bindable w => Bindable (PrimWidget w) where bind (PrimWidget {widgetPrim = w,guiDef = d}) a l = bind w a (wrapIOL (primAction d) l) -- listen to action commands by listening to the action event instance Has_Action w => Has_Action (PrimWidget w) where action (PrimWidget {actionPE = w,widgetPrim = wg}) l = do addListener w l -- action (PrimWidget {widgetPrim = w,guiDef = d}) l = action w (wrapIOL (primAction d) l) \end{code} helpful functions for applying config actions to prim widgets and making prim widgets. \begin{code} confPIO :: ConfigName -> (w -> IO ()) -> Conf (PrimWidget w) confPIO nm f = confIO' nm $ \w -> f (widgetPrim w) confPGUI :: ConfigName -> (w -> GUI ()) -> Conf (PrimWidget w) confPGUI nm f = confGUI' nm $ \w -> f (widgetPrim w) -- add a listener for a widget that is removed when the widget is destroyed addListenerW :: WidgetItem w => Event a -> Listener a -> w -> GUI () addListenerW e l w = liftIO $ do rm <- addListener e l addFinaliserW w rm -- on an action command apply a function to the widget, passing that value -- to the listener. Eg for a checkbutton which samples its own check value -- when clicked. commandSelf :: Has_Action w => (w -> GUI a) -> Listener a -> Conf w commandSelf f l = confGUI $ \w -> do l <- mapGUIL (const $ f w) l liftIO $ action w l return () -- make a standard prim widget, and action command prim widget, -- where we don't need access to the GUIInfo part. mkPWidget :: (WidgetItem w) => (WindowPrim -> [Config] -> IO w) -> [ConfigName] -> [Conf (PrimWidget w)] -> GUI (PrimWidget w) mkPWidget mk valids cs = mkPrimWidget (\w c _ -> mk w c) valids cs mkPWidgetAct :: (WidgetItem w,Has_Action w) => (WindowPrim -> [Config] -> IO w) -> [ConfigName] -> [Conf (PrimWidget w)] -> GUI (PrimWidget w) mkPWidgetAct mk valids cs = mkPrimWidgetAct (\w c _ -> mk w c) valids cs \end{code}