module GI.Properties ( genInterfaceProperties , genObjectProperties ) where #if !MIN_VERSION_base(4,8,0) import Control.Applicative ((<$>)) #endif import Control.Monad (forM_, when, unless) import Data.Text (Text) import qualified Data.Text as T import Foreign.Storable (sizeOf) import Foreign.C (CInt, CUInt) import GI.API import GI.Conversions import GI.Code import GI.GObject import GI.Inheritance (fullObjectPropertyList, fullInterfacePropertyList) import GI.SymbolNaming (upperName, classConstraint, qualify, hyphensToCamelCase) import GI.Type import GI.Util propTypeStr :: Type -> CodeGen Text propTypeStr t = case t of TBasicType TUTF8 -> return "String" TBasicType TFileName -> return "String" TBasicType TVoid -> return "Ptr" TByteArray -> return "ByteArray" TGHash _ _ -> return "Hash" TVariant -> return "Variant" TParamSpec -> return "ParamSpec" TBasicType TInt32 -> do -- This should work for all systems in common use, but rather -- than leaving the assumption implicit better double checking. when (sizeOf (0 :: CInt) /= 4) $ error "C Integers are not 4 bytes, unsupported platform." return "CInt" TBasicType TUInt32 -> do when (sizeOf (0 :: CUInt) /= 4) $ error "C Integers are not 4 bytes, unsupported platform." return "CUInt" TBasicType TInt64 -> return "Int64" TBasicType TUInt64 -> return "UInt64" TBasicType TBoolean -> return "Bool" TBasicType TFloat -> return "Float" TBasicType TDouble -> return "Double" TBasicType TGType -> return "GType" TCArray True _ _ (TBasicType TUTF8) -> return "StringArray" TCArray True _ _ (TBasicType TFileName) -> return "StringArray" TGList (TBasicType TVoid) -> return "PtrGList" t@(TInterface ns n) -> do api <- findAPIByName (Name ns n) case api of APIEnum _ -> return "Enum" APIFlags _ -> return "Flags" APIStruct s -> if structIsBoxed s then return "Boxed" else error $ "Unboxed struct property : " ++ show t APIUnion u -> if unionIsBoxed u then return "Boxed" else error $ "Unboxed union property : " ++ show t APIObject _ -> do isGO <- isGObject t if isGO then return "Object" else error $ "Non-GObject object property : " ++ show t APIInterface _ -> do isGO <- isGObject t if isGO then return "Object" else error $ "Non-GObject interface property : " ++ show t _ -> error $ "Unknown interface property of type : " ++ show t _ -> error $ "Don't know how to handle properties of type " ++ show t -- Given a property, return the set of constraints on the types, and -- the type variables for the object and its value. attrType :: Property -> CodeGen ([Text], Text) attrType prop = do (_,t,constraints) <- argumentType ['a'..'l'] $ propType prop if T.any (== ' ') t then return (constraints, parenthesize t) else return (constraints, t) genPropertySetter :: Name -> Text -> Property -> CodeGen () genPropertySetter n pName prop = group $ do oName <- upperName n (constraints, t) <- attrType prop let constraints' = "MonadIO m":(classConstraint oName <> " o"):constraints tStr <- propTypeStr $ propType prop line $ "set" <> pName <> " :: (" <> T.intercalate ", " constraints' <> ") => o -> " <> t <> " -> m ()" line $ "set" <> pName <> " obj val = liftIO $ setObjectProperty" <> tStr <> " obj \"" <> propName prop <> "\" val" genPropertyGetter :: Name -> Text -> Property -> CodeGen () genPropertyGetter n pName prop = group $ do oName <- upperName n outType <- haskellType (propType prop) let constraints = "(MonadIO m, " <> classConstraint oName <> " o)" line $ "get" <> pName <> " :: " <> constraints <> " => o -> " <> tshow ("m" `con` [outType]) tStr <- propTypeStr $ propType prop line $ "get" <> pName <> " obj = liftIO $ getObjectProperty" <> tStr <> " obj \"" <> propName prop <> "\"" <> if tStr `elem` ["Object", "Boxed"] then " " <> tshow outType -- These require the constructor too. else "" genPropertyConstructor :: Text -> Property -> CodeGen () genPropertyConstructor pName prop = group $ do (constraints, t) <- attrType prop tStr <- propTypeStr $ propType prop let constraints' = case constraints of [] -> "" _ -> parenthesize (T.intercalate ", " constraints) <> " => " line $ "construct" <> pName <> " :: " <> constraints' <> t <> " -> IO ([Char], GValue)" line $ "construct" <> pName <> " val = constructObjectProperty" <> tStr <> " \"" <> propName prop <> "\" val" -- | The property name as a lexically valid Haskell identifier. Note -- that this is not escaped, since it is assumed that it will be used -- with a prefix, so if a property is named "class", for example, this -- will return "class". hPropName :: Property -> Text hPropName = lcFirst . hyphensToCamelCase . propName genObjectProperties :: Name -> Object -> CodeGen () genObjectProperties n o = do isGO <- apiIsGObject n (APIObject o) -- We do not generate bindings for objects not descending from GObject. when isGO $ do allProps <- fullObjectPropertyList n o >>= mapM (\(owner, prop) -> do pi <- infoType owner prop return $ "'(\"" <> hPropName prop <> "\", " <> pi <> ")") genProperties n (objProperties o) allProps genInterfaceProperties :: Name -> Interface -> CodeGen () genInterfaceProperties n iface = do allProps <- fullInterfacePropertyList n iface >>= mapM (\(owner, prop) -> do pi <- infoType owner prop return $ "'(\"" <> hPropName prop <> "\", " <> pi <> ")") genProperties n (ifProperties iface) allProps -- If the given accesor is available (indicated by available == True), -- generate a fully qualified accesor name, otherwise just return -- "undefined". accessor is "get", "set" or "construct" accessorOrUndefined :: Bool -> Text -> Name -> Text -> CodeGen Text accessorOrUndefined available accessor (Name ons on) cName = if not available then return "undefined" else do prefix <- qualify ons return $ prefix <> accessor <> on <> cName -- | The name of the type encoding the information for the property of -- the object. infoType :: Name -> Property -> CodeGen Text infoType owner prop = do name <- upperName owner let cName = (hyphensToCamelCase . propName) prop return $ name <> cName <> "PropertyInfo" genOneProperty :: Name -> Property -> ExcCodeGen () genOneProperty owner prop = do name <- upperName owner let cName = (hyphensToCamelCase . propName) prop pName = name <> cName flags = propFlags prop writable = PropertyWritable `elem` flags && (PropertyConstructOnly `notElem` flags) readable = PropertyReadable `elem` flags constructOnly = PropertyConstructOnly `elem` flags -- For properties the meaning of having transfer /= TransferNothing -- is not clear (what are the right semantics for GValue setters?), -- and the other possibilities are very uncommon, so let us just -- assume that TransferNothing is always the case. when (propTransfer prop /= TransferNothing) $ notImplementedError $ "Property " <> pName <> " has unsupported transfer type " <> tshow (propTransfer prop) getter <- accessorOrUndefined readable "get" owner cName setter <- accessorOrUndefined writable "set" owner cName constructor <- accessorOrUndefined (writable || constructOnly) "construct" owner cName unless (readable || writable || constructOnly) $ notImplementedError $ "Property is not readable, writable, or constructible: " <> tshow pName group $ do line $ "-- VVV Prop \"" <> propName prop <> "\"" line $ " -- Type: " <> tshow (propType prop) line $ " -- Flags: " <> tshow (propFlags prop) when readable $ genPropertyGetter owner pName prop when writable $ genPropertySetter owner pName prop when (writable || constructOnly) $ genPropertyConstructor pName prop outType <- if not readable then return "()" else do sOutType <- tshow <$> haskellType (propType prop) return $ if T.any (== ' ') sOutType then parenthesize sOutType else sOutType -- Polymorphic _label style lens group $ do inIsGO <- isGObject (propType prop) hInType <- tshow <$> haskellType (propType prop) let inConstraint = if writable || constructOnly then if inIsGO then classConstraint hInType else "(~) " <> if T.any (== ' ') hInType then parenthesize hInType else hInType else "(~) ()" allowedOps = (if writable then ["'AttrSet", "'AttrConstruct"] else []) <> (if constructOnly then ["'AttrConstruct"] else []) <> (if readable then ["'AttrGet"] else []) it <- infoType owner prop exportProperty cName it when (getter /= "undefined") (exportProperty cName getter) when (setter /= "undefined") (exportProperty cName setter) when (constructor /= "undefined") (exportProperty cName constructor) bline $ "data " <> it line $ "instance AttrInfo " <> it <> " where" indent $ do line $ "type AttrAllowedOps " <> it <> " = '[ " <> T.intercalate ", " allowedOps <> "]" line $ "type AttrSetTypeConstraint " <> it <> " = " <> inConstraint line $ "type AttrBaseTypeConstraint " <> it <> " = " <> classConstraint name line $ "type AttrGetType " <> it <> " = " <> outType line $ "type AttrLabel " <> it <> " = \"" <> propName prop <> "\"" line $ "attrGet _ = " <> getter line $ "attrSet _ = " <> setter line $ "attrConstruct _ = " <> constructor -- | Generate a placeholder property for those cases in which code -- generation failed. genPlaceholderProperty :: Name -> Property -> CodeGen () genPlaceholderProperty owner prop = do line $ "-- XXX Placeholder" it <- infoType owner prop let cName = (hyphensToCamelCase . propName) prop exportProperty cName it line $ "data " <> it line $ "instance AttrInfo " <> it <> " where" indent $ do line $ "type AttrAllowedOps " <> it <> " = '[]" line $ "type AttrSetTypeConstraint " <> it <> " = (~) ()" line $ "type AttrBaseTypeConstraint " <> it <> " = (~) ()" line $ "type AttrGetType " <> it <> " = ()" line $ "type AttrLabel " <> it <> " = \"\"" line $ "attrGet = undefined" line $ "attrSet = undefined" line $ "attrConstruct = undefined" genProperties :: Name -> [Property] -> [Text] -> CodeGen () genProperties n ownedProps allProps = do name <- upperName n forM_ ownedProps $ \prop -> do handleCGExc (\err -> do line $ "-- XXX Generation of property \"" <> propName prop <> "\" of object \"" <> name <> "\" failed: " <> describeCGError err genPlaceholderProperty n prop) (genOneProperty n prop) group $ do let propListType = name <> "AttributeList" line $ "type instance AttributeList " <> name <> " = " <> propListType line $ "type " <> propListType <> " = ('[ " <> T.intercalate ", " allProps <> "] :: [(Symbol, *)])"