leksah-0.1: src/IDE/Framework/MakeEditor.hs
-----------------------------------------------------------------------------
--
-- Module : IDE.Framework.MakeEditor
-- Copyright : (c) Juergen Nicklisch-Franken (aka Jutaro)
-- License : GNU-GPL
--
-- Maintainer : Juergen Nicklisch-Franken <jnf at arcor.de>
-- Stability : experimental
-- Portability : portable
--
-- | Module for making editors out of descriptions
--
-----------------------------------------------------------------------------------
module IDE.Framework.MakeEditor (
FieldDescription(..)
, mkField
, emptyNotifier
, EventSelector(..)
, extractAndValidate
, extract
, mkEditor
, declareEvent
, activateEvent
, propagateEvent
, mkNotifier
) where
import Graphics.UI.Gtk hiding (Event)
import Control.Monad
import Data.IORef
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Unique
import IDE.Core.Exception
import IDE.Framework.Parameters
import IDE.Framework.EditorBasics
--
-- | A constructor type for a field desciption
--
type MkFieldDescription alpha beta =
Parameters ->
(Getter alpha beta) ->
(Setter alpha beta) ->
(Editor beta) ->
FieldDescription alpha
--
-- | A type to describe a field of a record, which can be edited
-- | alpha is the type of the individual field of the record
data FieldDescription alpha = FD {
parameters :: Parameters
, fieldEditor :: alpha -> IO (Widget, Injector alpha , alpha -> Extractor alpha , Notifier)
}
-- ------------------------------------------------------------
-- * Implementation of editing
-- ------------------------------------------------------------
--
-- | Function to construct a field description
--
mkField :: Eq beta => MkFieldDescription alpha beta
mkField parameters getter setter editor =
FD parameters
(\ dat -> do
(widget, inj,ext,noti) <- editor parameters
inj (getter dat)
noti FocusOut (Left (\e -> do
v <- ext
case v of
Just _ -> do
return False
Nothing -> do
sysMessage Normal "Validation Failure"
let message = case getParameterPrim paraName parameters of
Just s -> "in field " ++ s
Nothing -> "in unnamed field"
dia <- messageDialogNew Nothing [] MessageWarning ButtonsClose
("Validation Failure " ++ message)
dialogRun dia
widgetDestroy dia
return False))
return (widget,
(\a -> inj (getter a)),
(\a -> do
b <- ext
case b of
Just b -> return (Just (setter b a))
Nothing -> return Nothing),
noti))
-- | Function to construct an editor
--
mkEditor :: (Container -> Injector alpha) -> Extractor alpha -> Notifier -> Editor alpha
mkEditor injectorC extractor notifier parameters = do
let (xalign, yalign, xscale, yscale) = getParameter paraOuterAlignment parameters
outerAlig <- alignmentNew xalign yalign xscale yscale
let (paddingTop, paddingBottom, paddingLeft, paddingRight) = getParameter paraOuterPadding parameters
alignmentSetPadding outerAlig paddingTop paddingBottom paddingLeft paddingRight
frame <- frameNew
frameSetShadowType frame (getParameter paraShadow parameters)
case getParameter paraName parameters of
"" -> return ()
str -> frameSetLabel frame str
containerAdd outerAlig frame
let (xalign, yalign, xscale, yscale) = getParameter paraInnerAlignment parameters
innerAlig <- alignmentNew xalign yalign xscale yscale
let (paddingTop, paddingBottom, paddingLeft, paddingRight) = getParameter paraInnerPadding parameters
alignmentSetPadding innerAlig paddingTop paddingBottom paddingLeft paddingRight
containerAdd frame innerAlig
let (x,y) = getParameter paraMinSize parameters
widgetSetSizeRequest outerAlig x y
return ((castToWidget) outerAlig, injectorC (castToContainer innerAlig), extractor, notifier)
-- | Convenience method to validate and extract fields
--
extractAndValidate :: alpha -> [alpha -> Extractor alpha] -> [String] -> IO (Maybe alpha)
extractAndValidate val getExts fieldNames = do
(newVal,errors) <- foldM (\ (val,errs) (ext,fn) -> do
extVal <- ext val
case extVal of
Just nval -> return (nval,errs)
Nothing -> return (val, (' ' : fn) : errs))
(val,[]) (zip getExts fieldNames)
if null errors
then return (Just newVal)
else do
md <- messageDialogNew Nothing [] MessageWarning ButtonsClose
$ "The following fields have invalid values." ++
concat (reverse errors)
dialogRun md
widgetDestroy md
return Nothing
extract :: alpha -> [alpha -> Extractor alpha] -> IO (Maybe alpha)
extract val getExts =
foldM (\ mbVal ext -> do
case mbVal of
Nothing -> return Nothing
Just val -> do
newVal <- ext val
return newVal)
(Just val) getExts
-- ------------------------------------------------------------
-- * Implementation of notifications
-- ------------------------------------------------------------
--
-- | A type for a function to register an event
--
type RegFunc = Widget -> Handler -> IO (ConnectId Widget)
--
-- | A type for the state of the notification system
--
type NotifierSt = IORef (Map EventSelector
(Maybe Widget,RegFunc,Maybe (ConnectId Widget),[(Unique, Handler)]))
--
-- | Initial state of the notification system
--
emptyNotifier :: IO (NotifierSt)
emptyNotifier = newIORef(Map.empty)
--
-- | Declare that the event can be thrown from this editor
--
declareEvent :: EventSelector -> RegFunc -> NotifierSt -> IO()
declareEvent eventSel regFunc notifierState = do
noti <- readIORef notifierState
case Map.lookup eventSel noti of
Nothing -> do
let noti2 = Map.insert eventSel (Nothing, regFunc, Nothing,[]) noti
writeIORef notifierState noti2
Just _ -> throwIDE $"editor has already declared event " ++ show eventSel
--
-- | Activate the event after the widget has been constructed
--
activateEvent :: Widget -> EventSelector -> NotifierSt -> IO()
activateEvent widget eventSel notifierState = do
noti <- readIORef notifierState
case Map.lookup eventSel noti of
Nothing -> throwIDE $"editor has not declared event before activating it " ++ show eventSel
Just (Nothing,registerFunc,Nothing,handlers) -> do
cid <- registerFunc widget (\ e -> do
noti <- readIORef notifierState
case Map.lookup eventSel noti of
Nothing -> return False
Just (_,_,_,[]) -> return False
Just (_,_,_,handlers2) -> do
boolList <- mapM (\f -> f e) (map snd handlers2)
return (foldl (&&) True boolList))
let noti2 = Map.insert eventSel (Just widget,registerFunc,Just cid,handlers) noti
writeIORef notifierState noti2
Just _ -> throwIDE $"editor has already been activated " ++ show eventSel
--
-- | Propagate the event with the selector from notifier to notifierst
--
propagateEvent :: EventSelector -> Notifier -> NotifierSt -> IO()
propagateEvent eventSel notiFrom notifierState = do
noti <- readIORef notifierState
case Map.lookup eventSel noti of
Nothing -> throwIDE $"can't propagte event which is not activated " ++ show eventSel
Just (mbWidget,registerFunc,Nothing,handlers) -> do
cid <- notiFrom eventSel (Left (\ e -> do
noti <- readIORef notifierState
case Map.lookup eventSel noti of
Nothing -> return False
Just (_,_,_,[]) -> return False
Just (_,_,_,handlers2) -> do
boolList <- mapM (\f -> f e) (map snd handlers2)
return (foldl (&&) True boolList)))
let noti2 = Map.insert eventSel (mbWidget,registerFunc,Nothing,handlers) noti
writeIORef notifierState noti2
Just _ -> throwIDE $"editor has already been activated " ++ show eventSel
--
-- | Constructor for a notifier
--
mkNotifier :: NotifierSt -> Notifier
mkNotifier notifierState = notFunc
where
notFunc :: EventSelector -> Either Handler Unique -> IO (Unique)
notFunc eventSel (Left handler) = do
noti <- readIORef notifierState
uni <- newUnique
case Map.lookup eventSel noti of
Nothing -> throwIDE $"editor does not support event " ++ show eventSel
Just (Just widget,registerFunc,Nothing,handlers)
-> throwIDE $"mkNotifier for activated event" ++ show eventSel
Just (mbWidget, registerFunc, mbUnique, handlers)
-> do unique <- newUnique
let noti2 = Map.insert eventSel
(mbWidget,registerFunc,mbUnique,handlers++[(uni,handler)]) noti
writeIORef notifierState noti2
return unique
notFunc eventSel (Right uni) = do
noti <- readIORef notifierState
case Map.lookup eventSel noti of
Nothing -> throwIDE $"editor does not support event " ++ show eventSel
Just (mbWidget,regFunc,Just cid,l) -> do
let l2 = filter (\(u,_) -> u /= uni) l
if null l2
then do
signalDisconnect cid
let noti2 = Map.insert eventSel (mbWidget,regFunc,Nothing,[]) noti
writeIORef notifierState noti2
else do
let noti2 = Map.insert eventSel (mbWidget,regFunc,Just cid,l2) noti
writeIORef notifierState noti2
_ -> return ()
return uni