{-# LANGUAGE ScopedTypeVariables, TypeFamilies, FlexibleContexts, FlexibleInstances #-} -- | Facilities for defining new object types which can be marshalled between -- Haskell and QML. module Graphics.QML.Objects ( -- * Class Definition Object ( classDef), ClassDef, Member, defClass, -- * Methods defMethod, MethodSuffix, -- * Properties defPropertyRO, defPropertyRW, -- * Signals defSignal, fireSignal, SignalKey ( type SignalParams), SignalSuffix, -- * Object References ObjRef, newObject, fromObjRef, -- * Dynamic Object References AnyObjRef, anyObjRef, fromAnyObjRef, -- * Customer Marshallers objSimpleMarshaller, objBidiMarshaller ) where import Graphics.QML.Internal.BindCore import Graphics.QML.Internal.BindObj import Graphics.QML.Internal.JobQueue import Graphics.QML.Internal.Marshal import Graphics.QML.Internal.MetaObj import Graphics.QML.Internal.Objects import Control.Concurrent.MVar import Control.Monad.Trans.Maybe import Data.Map (Map) import qualified Data.Map as Map import Data.Maybe import Data.Tagged import Data.Typeable import Data.IORef import Foreign.Ptr import Foreign.Storable import Foreign.Marshal.Alloc import Foreign.Marshal.Array import System.IO.Unsafe import Numeric -- -- ObjRef -- -- | Represents an instance of the QML class which wraps the type @tt@. data ObjRef tt = ObjRef { objHndl :: HsQMLObjectHandle } instance (Object tt) => Marshal (ObjRef tt) where type MarshalMode (ObjRef tt) = ValObjBidi tt marshaller = MValObjBidi { mValObjBidi_typeName = Tagged $ TypeName "QObject*", mValObjBidi_valToHs = \ptr -> do any <- mValToHs ptr MaybeT $ return $ fromAnyObjRef any, mValObjBidi_hsToVal = \obj ptr -> mHsToVal (AnyObjRef $ objHndl obj) ptr, mValObjBidi_hsToAlloc = \obj f -> mHsToAlloc (AnyObjRef $ objHndl obj) f, mValObjBidi_objToHs = \hndl -> return $ ObjRef hndl, mValObjBidi_hsToObj = \obj -> return $ objHndl obj} -- | Creates an instance of a QML class given a value of the underlying Haskell -- type @tt@. newObject :: forall tt. (Object tt) => tt -> IO (ObjRef tt) newObject obj = do cRec <- getClassRec (classDef :: ClassDef tt) oHndl <- hsqmlCreateObject obj $ crecHndl cRec return $ ObjRef oHndl -- | Returns the associated value of the underlying Haskell type @tt@ from an -- instance of the QML class which wraps it. fromObjRef :: ObjRef tt -> tt fromObjRef = unsafePerformIO . fromObjRefIO fromObjRefIO :: ObjRef tt -> IO tt fromObjRefIO = hsqmlObjectGetHaskell . objHndl -- | Represents an instance of a QML class which wraps an arbitrary Haskell -- type. Unlike 'ObjRef', an 'AnyObjRef' only carries the type of its Haskell -- value dynamically and does not encode it into the static type. data AnyObjRef = AnyObjRef { anyObjHndl :: HsQMLObjectHandle } instance Marshal AnyObjRef where type MarshalMode AnyObjRef = ValObjBidi () marshaller = MValObjBidi { mValObjBidi_typeName = Tagged $ TypeName "QObject*", mValObjBidi_valToHs = \ptr -> MaybeT $ do objPtr <- peek (castPtr ptr) hndl <- hsqmlGetObjectHandle objPtr return $ if isNullObjectHandle hndl then Nothing else Just $ AnyObjRef hndl, mValObjBidi_hsToVal = \obj ptr -> do objPtr <- hsqmlObjectGetPointer $ anyObjHndl obj poke (castPtr ptr) objPtr, mValObjBidi_hsToAlloc = \obj f -> alloca $ \(ptr :: Ptr (Ptr ())) -> flip mHsToVal (castPtr ptr) obj >> f (castPtr ptr), mValObjBidi_objToHs = \hndl -> return $ AnyObjRef hndl, mValObjBidi_hsToObj = \obj -> return $ anyObjHndl obj} -- | Upcasts an 'ObjRef' into an 'AnyObjRef'. anyObjRef :: ObjRef tt -> AnyObjRef anyObjRef (ObjRef hndl) = AnyObjRef hndl -- | Attempts to downcast an 'AnyObjRef' into an 'ObjRef' with the specific -- underlying Haskell type @tt@. fromAnyObjRef :: (Object tt) => AnyObjRef -> Maybe (ObjRef tt) fromAnyObjRef = unsafePerformIO . fromAnyObjRefIO fromAnyObjRefIO :: forall tt. (Object tt) => AnyObjRef -> IO (Maybe (ObjRef tt)) fromAnyObjRefIO (AnyObjRef hndl) = do let srcRep = typeOf (undefined :: tt) dstRep <- hsqmlObjectGetHsTyperep hndl if srcRep == dstRep then return $ Just $ ObjRef hndl else return Nothing -- | Provides a QML-to-Haskell 'Marshaller' which allows you to define -- instances of 'Marshal' for custom 'Object' types. This allows a custom types -- to be passed into Haskell code as method parameters without having to -- manually deal with 'ObjRef's. -- -- For example, an instance for 'MyObjectType' would be defined as follows: -- -- @ -- instance Marshal MyObjectType where -- type MarshalMode MyObjectType = ValObjToOnly MyObjectType -- marshaller = objSimpleMarshaller -- @ objSimpleMarshaller :: forall obj. (Object obj) => Marshaller obj (ValObjToOnly obj) objSimpleMarshaller = MValObjToOnly { mValObjToOnly_typeName = retag (mTypeName :: Tagged (ObjRef obj) TypeName), mValObjToOnly_valToHs = \ptr -> (errIO . fromObjRefIO) =<< mValToHs ptr, mValObjToOnly_objToHs = hsqmlObjectGetHaskell} -- | Provides a bidirectional QML-to-Haskell and Haskell-to-QML 'Marshaller' -- which allows you to define instances of 'Marshal' for custom object types. -- This allows a custom type to be passed in and out of Haskell code via -- methods, properties, and signals, without having to manually deal with -- 'ObjRef's. Unlike the simple marshaller, this one must be given a function -- which specifies how to obtain an 'ObjRef' given a Haskell value. -- -- For example, an instance for 'MyObjectType' which simply creates a new -- object whenever one is required would be defined as follows: -- -- @ -- instance Marshal MyObjectType where -- type MarshalMode MyObjectType = ValObjBidi MyObjectType -- marshaller = objBidiMarshaller newObject -- @ objBidiMarshaller :: forall obj. (Object obj) => (obj -> IO (ObjRef obj)) -> Marshaller obj (ValObjBidi obj) objBidiMarshaller newFn = MValObjBidi { mValObjBidi_typeName = retag (mTypeName :: Tagged (ObjRef obj) TypeName), mValObjBidi_valToHs = \ptr -> (errIO . fromObjRefIO) =<< mValToHs ptr, mValObjBidi_hsToVal = \obj ptr -> flip mHsToVal ptr =<< newFn obj, mValObjBidi_hsToAlloc = \obj f -> flip mHsToAlloc f =<< newFn obj, mValObjBidi_objToHs = hsqmlObjectGetHaskell, mValObjBidi_hsToObj = fmap objHndl . newFn} -- -- ClassDef -- -- | Generates a 'ClassDef' from a list of 'Member's. defClass :: forall tt. (Object tt) => [Member tt] -> ClassDef tt defClass = ClassDef data MemoStore k v = MemoStore (MVar (Map k v)) (IORef (Map k v)) newMemoStore :: IO (MemoStore k v) newMemoStore = do let m = Map.empty mr <- newMVar m ir <- newIORef m return $ MemoStore mr ir getFromMemoStore :: (Ord k) => MemoStore k v -> k -> IO v -> IO (Bool, v) getFromMemoStore (MemoStore mr ir) key fn = do fstMap <- readIORef ir case Map.lookup key fstMap of Just val -> return (False, val) Nothing -> modifyMVar mr $ \sndMap -> do case Map.lookup key sndMap of Just val -> return (sndMap, (False, val)) Nothing -> do val <- fn let newMap = Map.insert key val sndMap writeIORef ir newMap return (newMap, (True, val)) data ClassRec = ClassRec { crecHndl :: HsQMLClassHandle, crecSigs :: Map TypeRep Int } {-# NOINLINE classRecDb #-} classRecDb :: MemoStore TypeRep ClassRec classRecDb = unsafePerformIO $ newMemoStore getClassRec :: forall tt. (Object tt) => ClassDef tt -> IO ClassRec getClassRec cdef = do let typ = typeOf (undefined :: tt) (_, val) <- getFromMemoStore classRecDb typ (createClass typ cdef) return val createClass :: forall tt. (Object tt) => TypeRep -> ClassDef tt -> IO ClassRec createClass typRep cdef = do hsqmlInit classId <- hsqmlGetNextClassId let constrs t = typeRepTyCon t : (concatMap constrs $ typeRepArgs t) name = foldr (\c s -> showString (tyConName c) . showChar '_' . s) id (constrs typRep) $ showInt classId "" ms = classMembers cdef moc = compileClass name ms sigs = filterMembers SignalMember ms sigMap = Map.fromList $ flip zip [0..] $ map (fromJust . memberKey) sigs maybeMarshalFunc = maybe (return nullFunPtr) marshalFunc metaDataPtr <- crlToNewArray return (mData moc) metaStrDataPtr <- crlToNewArray return (mStrData moc) methodsPtr <- crlToNewArray maybeMarshalFunc (mFuncMethods moc) propsPtr <- crlToNewArray maybeMarshalFunc (mFuncProperties moc) maybeHndl <- hsqmlCreateClass metaDataPtr metaStrDataPtr typRep methodsPtr propsPtr case maybeHndl of Just hndl -> return $ ClassRec hndl sigMap Nothing -> error ("Failed to create QML class '"++name++"'.") -- -- Method -- data MethodTypeInfo = MethodTypeInfo { methodParamTypes :: [TypeName], methodReturnType :: TypeName } newtype MSHelp a = MSHelp a -- | Supports marshalling Haskell functions with an arbitrary number of -- arguments. class MethodSuffix a where mkMethodFunc :: Int -> a -> Ptr (Ptr ()) -> ErrIO () mkMethodTypes :: Tagged a MethodTypeInfo instance (Marshal a, MarshalToHs (MarshalMode a), MethodSuffix (MSHelp b)) => MethodSuffix (MSHelp (a -> b)) where mkMethodFunc n (MSHelp f) pv = do ptr <- errIO $ peekElemOff pv n val <- mValToHs ptr mkMethodFunc (n+1) (MSHelp $ f val) pv return () mkMethodTypes = let (MethodTypeInfo p r) = untag (mkMethodTypes :: Tagged (MSHelp b) MethodTypeInfo) typ = untag (mTypeName :: Tagged a TypeName) in Tagged $ MethodTypeInfo (typ:p) r instance (Marshal a, MarshalToValRaw (MarshalMode a)) => MethodSuffix (MSHelp (IO a)) where mkMethodFunc _ (MSHelp f) pv = errIO $ do ptr <- peekElemOff pv 0 val <- f if nullPtr == ptr then return () else mHsToVal val ptr mkMethodTypes = let typ = untag (mTypeName :: Tagged a TypeName) in Tagged $ MethodTypeInfo [] typ mkUniformFunc :: forall tt ms. (Marshal tt, MarshalFromObj (MarshalMode tt), MethodSuffix (MSHelp ms)) => (tt -> ms) -> UniformFunc mkUniformFunc f = \pt pv -> do hndl <- hsqmlGetObjectHandle pt this <- mObjToHs hndl runErrIO $ mkMethodFunc 1 (MSHelp $ f this) pv newtype VoidIO = VoidIO {runVoidIO :: (IO ())} instance MethodSuffix (MSHelp VoidIO) where mkMethodFunc _ (MSHelp f) pv = errIO $ runVoidIO f mkMethodTypes = Tagged $ MethodTypeInfo [] (TypeName "") class IsVoidIO a instance (IsVoidIO b) => IsVoidIO (a -> b) instance IsVoidIO VoidIO mkSpecialFunc :: forall tt ms. (Marshal tt, MarshalFromObj (MarshalMode tt), MethodSuffix (MSHelp ms), IsVoidIO ms) => (tt -> ms) -> UniformFunc mkSpecialFunc f = \pt pv -> do hndl <- hsqmlGetObjectHandle pt this <- mObjToHs hndl runErrIO $ mkMethodFunc 0 (MSHelp $ f this) pv -- | Defines a named method using a function @f@ in the IO monad. -- -- The first argument to @f@ receives the \"this\" object and hence must match -- the type of the class on which the method is being defined. Subsequently, -- there may be zero or more parameter arguments followed by an optional return -- argument in the IO monad. defMethod :: forall tt ms. (Marshal tt, MarshalFromObj (MarshalMode tt), MethodSuffix (MSHelp ms)) => String -> (tt -> ms) -> Member (ThisObj tt) defMethod name f = let crude = untag (mkMethodTypes :: Tagged (MSHelp ms) MethodTypeInfo) in Member MethodMember name (methodReturnType crude) (methodParamTypes crude) (mkUniformFunc f) Nothing Nothing -- -- Property -- -- | Defines a named read-only property using an accessor function in the IO -- monad. defPropertyRO :: forall tt tr. (Marshal tt, MarshalFromObj (MarshalMode tt), Marshal tr, MarshalToVal (MarshalMode tr)) => String -> (tt -> IO tr) -> Member (ThisObj tt) defPropertyRO name g = Member PropertyMember name (untag (mTypeName :: Tagged tr TypeName)) [] (mkUniformFunc g) Nothing Nothing -- | Defines a named read-write property using a pair of accessor and mutator -- functions in the IO monad. defPropertyRW :: forall tt tr. (Marshal tt, MarshalFromObj (MarshalMode tt), Marshal tr, MarshalToHs (MarshalMode tr), MarshalToVal (MarshalMode tr)) => String -> (tt -> IO tr) -> (tt -> tr -> IO ()) -> Member (ThisObj tt) defPropertyRW name g s = Member PropertyMember name (untag (mTypeName :: Tagged tr TypeName)) [] (mkUniformFunc g) (Just $ mkSpecialFunc (\a b -> VoidIO $ s a b)) Nothing -- -- Signal -- data SignalTypeInfo = SignalTypeInfo { signalParamTypes :: [TypeName] } -- | Defines a named signal using a 'SignalKey'. defSignal :: forall obj sk. (Object obj, SignalKey sk) => Tagged sk String -> Member obj defSignal tn = let crude = untag (mkSignalTypes :: Tagged (SignalParams sk) SignalTypeInfo) in Member SignalMember (untag tn) (TypeName "") (signalParamTypes crude) (\_ _ -> return ()) Nothing (Just $ typeOf (undefined :: sk)) -- | Fires a signal on an 'Object', specified using a 'SignalKey'. fireSignal :: forall tt sk. ( Marshal tt, MarshalToObj (MarshalMode tt), Object (ThisObj tt), SignalKey sk) => Tagged sk tt -> SignalParams sk fireSignal this = let start cnt = do crec <- getClassRec (classDef :: ClassDef (ThisObj tt)) let keyRep = typeOf (undefined :: sk) slotMay = Map.lookup keyRep $ crecSigs crec case slotMay of Just slotIdx -> postJob $ do hndl <- mHsToObj $ untag this withActiveObject hndl $ cnt $ SignalData hndl slotIdx Nothing -> error ("Attempt to fire undefined signal on class '"++ (typeName $ untag (mTypeName :: Tagged tt TypeName))++"'.") cont ps (SignalData hndl slotIdx) = withArray (nullPtr:ps) (\pptr -> hsqmlFireSignal hndl slotIdx pptr) in mkSignalArgs start cont data SignalData = SignalData HsQMLObjectHandle Int -- | Instances of the 'SignalKey' class identify distinct signals. The -- associated 'SignalParams' type specifies the signal's signature. class (SignalSuffix (SignalParams sk), Typeable sk) => SignalKey sk where type SignalParams sk -- | Supports marshalling an arbitrary number of arguments into a QML signal. class SignalSuffix ss where mkSignalArgs :: forall usr. ((usr -> IO ()) -> IO ()) -> ([Ptr ()] -> usr -> IO ()) -> ss mkSignalTypes :: Tagged ss SignalTypeInfo instance (Marshal a, MarshalToVal (MarshalMode a), SignalSuffix b) => SignalSuffix (a -> b) where mkSignalArgs start cont param = mkSignalArgs start (\ps usr -> mHsToAlloc param (\ptr -> cont (ptr:ps) usr)) mkSignalTypes = let (SignalTypeInfo p) = untag (mkSignalTypes :: Tagged b SignalTypeInfo) typ = untag (mTypeName :: Tagged a TypeName) in Tagged $ SignalTypeInfo (typ:p) instance SignalSuffix (IO ()) where mkSignalArgs start cont = start $ cont [] mkSignalTypes = Tagged $ SignalTypeInfo []