haskell-gi-0.8: src/GI/OverloadedSignals.hs
module GI.OverloadedSignals
( genSignalInstances
, genOverloadedSignalConnectors
) where
#if !MIN_VERSION_base(4,8,0)
import Control.Applicative ((<$>))
#endif
import Control.Monad (forM_, when)
import Control.Monad.Writer (tell)
import Data.Text (Text)
import qualified Data.Text as T
import Data.List (intercalate)
import qualified Data.Set as S
import GI.API
import GI.Code
import GI.Inheritance (fullObjectSignalList, fullInterfaceSignalList)
import GI.GObject (apiIsGObject)
import GI.Signal (signalHaskellName)
import GI.SymbolNaming (ucFirst, upperName)
import GI.Util (padTo)
-- A list of distinct signal names for all GObjects appearing in the
-- given list of APIs.
findSignalNames :: [(Name, API)] -> CodeGen [String]
findSignalNames apis = (map T.unpack . S.toList) <$> go apis S.empty
where
go :: [(Name, API)] -> S.Set Text -> CodeGen (S.Set Text)
go [] set = return set
go ((_, api):apis) set =
case api of
APIInterface iface ->
go apis $ insertSignals (ifSignals iface) set
APIObject object ->
go apis $ insertSignals (objSignals object) set
_ -> go apis set
insertSignals :: [Signal] -> S.Set Text -> S.Set Text
insertSignals props set = foldr (S.insert . sigName) set props
-- | Generate the overloaded signal connectors: "Clicked", "ActivateLink", ...
genOverloadedSignalConnectors :: [(Name, API)] -> String -> CodeGen ()
genOverloadedSignalConnectors allAPIs modulePrefix = do
line "-- Generated code."
blank
line "{-# LANGUAGE DataKinds, GADTs, KindSignatures, FlexibleInstances #-}"
blank
line $ "module " ++ modulePrefix ++ "Signals where"
blank
line "import GHC.TypeLits"
line "import GHC.Exts (Constraint)"
blank
line "class NoConstraint a"
line "instance NoConstraint a"
blank
line "data SignalProxy (a :: Symbol) (b :: Symbol) (c :: * -> Constraint) where"
indent $ do
signalNames <- findSignalNames allAPIs
let maxLength = maximum $ map (length . signalHaskellName) signalNames
forM_ signalNames $ \sn ->
line $ padTo (maxLength + 1) (ucFirst (signalHaskellName sn)) ++
":: SignalProxy \"" ++ sn ++ "\" \"\" NoConstraint"
-- | Qualified name for the "(sigName, info)" tag for a given signal.
signalInfoName :: Name -> Signal -> CodeGen String
signalInfoName n signal = do
n' <- upperName n
return $ n' ++ (ucFirst . signalHaskellName . T.unpack . sigName) signal
++ "SignalInfo"
-- | Generate the given signal instance for the given API object.
genInstance :: Name -> Signal -> CodeGen ()
genInstance owner signal = group $ do
name <- upperName owner
let sn = (ucFirst . signalHaskellName . T.unpack . sigName) signal
si <- signalInfoName owner signal
line $ "data " ++ si
line $ "instance SignalInfo " ++ si ++ " where"
indent $ do
let signalConnectorName = name ++ sn
cbHaskellType = signalConnectorName ++ "Callback"
line $ "type HaskellCallbackType " ++ si ++ " = " ++ cbHaskellType
line $ "connectSignal _ = " ++ "connect" ++ name ++ sn
-- | Signal instances for (GObject-derived) objects.
genObjectSignals :: Name -> Object -> CodeGen ()
genObjectSignals n o = do
name <- upperName n
isGO <- apiIsGObject n (APIObject o)
when isGO $ do
mapM_ (genInstance n) (objSignals o)
infos <- fullObjectSignalList n o >>=
mapM (\(owner, signal) -> do
si <- signalInfoName owner signal
return $ "'(\"" ++ (T.unpack . sigName) signal
++ "\", " ++ si ++ ")")
-- The "notify::[property]" signal is a generic signal used for
-- connecting to property notifications.
let allSignals = infos ++
["'(\"notify::[property]\", GObjectNotifySignalInfo)"]
group . line $ "type instance SignalList " ++ name ++
" = '[ " ++ intercalate ", " allSignals ++ "]"
-- | Signal instances for interfaces.
genInterfaceSignals :: Name -> Interface -> CodeGen ()
genInterfaceSignals n iface = do
name <- upperName n
mapM_ (genInstance n) (ifSignals iface)
infos <- fullInterfaceSignalList n iface >>=
mapM (\(owner, signal) -> do
si <- signalInfoName owner signal
return $ "'(\"" ++ T.unpack (sigName signal)
++ "\", " ++ si ++ ")")
isGO <- apiIsGObject n (APIInterface iface)
-- The "notify::[property]" signal is a generic signal used for
-- connecting to property notifications of a GObject.
let allSignals =
if isGO
then infos ++ ["'(\"notify::[property]\", GObjectNotifySignalInfo)"]
else infos
group . line $ "type instance SignalList " ++ name ++
" = '[ " ++ intercalate ", " allSignals ++ "]"
-- | Generate HasSignal instances for a given API element.
genSignals :: (Name, API) -> CodeGen ()
genSignals (n, APIObject o) = genObjectSignals n o
genSignals (n, APIInterface i) = genInterfaceSignals n i
genSignals _ = return ()
-- | Generate the signal information instances, so the generic overloaded
-- signal connectors are available.
genSignalInstances :: String -> [(Name, API)] -> String -> CodeGen ()
genSignalInstances name apis modulePrefix = do
let mp = (modulePrefix ++)
nm = ucFirst name
code <- recurse' $ forM_ apis genSignals
line "-- Generated code."
blank
-- Providing orphan instances is the whole point of these modules,
-- tell GHC that this is fine.
line "{-# OPTIONS_GHC -fno-warn-orphans #-}"
blank
line "{-# LANGUAGE DataKinds, FlexibleInstances,"
line " TypeFamilies, MultiParamTypeClasses,"
line " TypeOperators #-}"
blank
line $ "module " ++ mp nm ++ "Signals where"
blank
line "import Data.GI.Base.Properties (GObjectNotifySignalInfo)"
line "import Data.GI.Base.Signals"
line "import Data.GI.Base.Overloading"
blank
-- Import dependencies, including instances for their overloaded
-- signals, so they are implicitly reexported and they do not need
-- to be included explicitly from client code.
deps <- S.toList <$> getDeps
forM_ deps $ \i -> when (i /= name) $
line $ "import qualified " ++ mp (ucFirst i) ++ "Signals as " ++ ucFirst i
line $ "import " ++ modulePrefix ++ nm
blank
tell code