packages feed

haskell-gi-0.12: src/GI/OverloadedSignals.hs

module GI.OverloadedSignals
    ( genObjectSignals
    , genInterfaceSignals
    , genOverloadedSignalConnectors
    ) where

#if !MIN_VERSION_base(4,8,0)
import Control.Applicative ((<$>))
#endif
import Control.Monad (forM_, when)
import Data.Text (Text)
import qualified Data.Text as T

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 (upperName)
import GI.Util (padTo, ucFirst)

-- A list of distinct signal names for all GObjects appearing in the
-- given list of APIs.
findSignalNames :: [(Name, API)] -> CodeGen [Text]
findSignalNames apis = 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)] -> CodeGen ()
genOverloadedSignalConnectors allAPIs = do
  setLanguagePragmas ["DataKinds", "GADTs", "KindSignatures", "FlexibleInstances"]
  setModuleFlags [ImplicitPrelude, NoTypesImport, NoCallbacksImport]

  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 (T.length . signalHaskellName) signalNames
    forM_ signalNames $ \sn ->
        line $ padTo (maxLength + 1) (ucFirst (signalHaskellName sn)) <>
                 ":: SignalProxy \"" <> sn <> "\" \"\" NoConstraint"
  exportToplevel "SignalProxy(..)"

-- | Qualified name for the "(sigName, info)" tag for a given signal.
signalInfoName :: Name -> Signal -> CodeGen Text
signalInfoName n signal = do
  n' <- upperName n
  return $ n' <> (ucFirst . signalHaskellName . 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 . sigName) signal
  si <- signalInfoName owner signal
  bline $ "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
  exportSignal sn si

-- | 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 $ "'(\"" <> sigName signal
                                 <> "\", " <> si <> ")")
       -- The "notify::[property]" signal is a generic signal used for
       -- connecting to property notifications.
       let allSignals = infos <>
                        ["'(\"notify::[property]\", GObjectNotifySignalInfo)"]
       group $ do
         let signalListType = name <> "SignalList"
         line $ "type instance SignalList " <> name <> " = " <> signalListType
         line $ "type " <> signalListType <> " = ('[ "
                  <> T.intercalate ", " allSignals <> "] :: [(Symbol, *)])"

-- | 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 $ "'(\"" <> 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 $ do
    let signalListType = name <> "SignalList"
    line $ "type instance SignalList " <> name <> " = " <> signalListType
    line $ "type " <> signalListType <> " = ('[ "
             <> T.intercalate ", " allSignals <> "] :: [(Symbol, *)])"