packages feed

qtah-qt5-0.1.0: src/Graphics/UI/Qtah/Event.hs

-- This file is part of Qtah.
--
-- Copyright 2016 Bryan Gardiner <bog@khumba.net>
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU Lesser General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU Lesser General Public License for more details.
--
-- You should have received a copy of the GNU Lesser General Public License
-- along with this program.  If not, see <http://www.gnu.org/licenses/>.

{-# LANGUAGE ExistentialQuantification #-}

-- | General routines for managing 'QEvent's.
module Graphics.UI.Qtah.Event (
  -- * High-level interface.
  Event (..),
  EventRegistration,
  unregister,
  -- * Low-level interface
  EventFilter,
  onAnyEvent,
  -- * Internal
  internalOnEvent,
  ) where

import Control.Concurrent.MVar (MVar, modifyMVar_, newMVar)
import Control.Monad (when)
import Foreign.C.Types (CInt)
import Foreign.Hoppy.Runtime (delete)
import Foreign.Ptr (Ptr, nullPtr)
import Graphics.UI.Qtah.Core.QObject (QObject, QObjectPtr)
import qualified Graphics.UI.Qtah.Core.QObject as QObject
-- Note, Generated import, since the non-Generated import imports this module.
import Graphics.UI.Qtah.Generated.Core.QEvent (QEvent)
import Graphics.UI.Qtah.Internal.EventListener (EventListener)
import qualified Graphics.UI.Qtah.Internal.EventListener as EventListener
import Graphics.UI.Qtah.Signal (connect)

data Receiver = forall a. QObjectPtr a => Receiver a

-- | A typeclass for Qt event classes (subclasses of @QEvent@).
class Event e where
  -- | Registers a callback function to be invoked when an event of type @e@ is
  -- sent to an object.  This is a wrapper around 'onAnyEvent', so for details,
  -- see that function; all comments about @EventFilter@s apply equally to
  -- handlers given here.
  onEvent :: QObjectPtr this => this -> (e -> IO Bool) -> IO EventRegistration

-- | A record that an event handler was registered with a receiver object.  This
-- can be given to 'unregister' to destroy the corresponding handler.
data EventRegistration = EventRegistration
  { regReceiver :: Receiver
  , regListener :: EventListener
  , regActive :: MVar Bool
  }

-- | An filter that can handle any type of event.
type EventFilter = QObject -> QEvent -> IO Bool

-- | Registers an 'EventFilter' to listen to events that a 'QObject' receives.
-- A filter can return false to allow the event to propagate further, or true to
-- indicate that the event has been handled, and stop propagation.  When
-- multiple filters are attached to an object, the last one installed is called
-- first.  The filter will stay active until the receiver is deleted, or
-- 'unregister' is called.
--
-- This function uses 'QObject.installEventFilter' under the hood.
onAnyEvent :: QObjectPtr target => target -> EventFilter -> IO EventRegistration
onAnyEvent receiver filter = internalOnEvent receiver nullPtr filter

-- | Internal function, do not use outside of Qtah.
--
-- Implements 'onAnyEvent'.  Also takes a pointer to an @int@ that is passed to
-- the underlying 'EventListener.EventListener' object to be set to 1 when the
-- listener is deleted.  This is used for testing purposes.
internalOnEvent :: QObjectPtr target => target -> Ptr CInt -> EventFilter -> IO EventRegistration
internalOnEvent receiver deletedPtr filter = do
  listener <- EventListener.new filter deletedPtr
  activeVar <- newMVar True
  let reg = EventRegistration
            { regReceiver = Receiver receiver
            , regListener = listener
            , regActive = activeVar
            }
  QObject.installEventFilter receiver listener
  _ <- connect receiver QObject.destroyedSignal $ \_ -> unregister reg
  return reg

-- | Disconnects an event handler and frees its resources.  This function is
-- idempotent.
unregister :: EventRegistration -> IO ()
unregister reg = modifyMVar_ (regActive reg) $ \active -> do
  when active $ do
    let listener = regListener reg
    case regReceiver reg of
      Receiver receiver -> QObject.removeEventFilter receiver listener
    delete listener
  return False