packages feed

billeksah-forms-1.0.0: src/Graphics/Forms/GUIEvent.hs

{-# Language MultiParamTypeClasses, ScopedTypeVariables, FlexibleContexts, RankNTypes,
    ExistentialQuantification, DeriveDataTypeable, StandaloneDeriving, TypeSynonymInstances,
    FlexibleInstances, TypeFamilies #-}

-----------------------------------------------------------------------------
--
-- Module      :  Graphics.Forms.GUIEvent
-- Copyright   :  (c) Juergen Nicklisch-Franken, Hamish Mackenzie
-- License     :  GNU-GPL
--
-- Maintainer  :  <maintainer at leksah.org>
-- Stability   :  provisional
-- Portability :  portable
--
-- | Module for handling gui events by our own event system
--
-----------------------------------------------------------------------------------

module Graphics.Forms.GUIEvent (

    getStandardRegFunction
,   GtkRegFunc
,   GtkHandler
,   Connection
,   Connections
,   GtkRegMap(..)
,   GuiHandlerStateSel(..)
,   GtkEventsStateSel(..)

,   activateGUIEvent
,   activateGUIEvent'
,   makeGUIEvent
,   registerGUIEvent
,   triggerGUIEvent
,   propagateGUIEvent
,   retriggerAsChanged
,   dummyGUIEvent
) where

import Base
import Graphics.Pane
import Graphics.Forms.Basics
       (GUIEventSelector(..), GUIEvent(..), GEvent)



import Graphics.UI.Gtk
import qualified Graphics.UI.Gtk.Gdk.Events as Gtk
import Data.Unique
import Control.Monad
import Data.Map (Map)
import qualified Data.Map as Map  (insert,lookup,empty)
import Data.Typeable (Typeable)


-- ------------------------------------------------------------
-- * Implementation of GUI event system
-- ------------------------------------------------------------

--  | A type for handling an IO event
--  Returning True: The event has been handles
--  Returning False: Handling should proceed
type GtkHandler = Gtk.Event -> IO Bool

--
-- | A type for a function to register a gtk event
-- |
type GtkRegFunc = forall o . GObjectClass o => o -> GtkHandler -> IO (Connection)

--
-- | The widgets are the real event sources.
-- The GtkRegFunc is the function used to register the event.
-- The connectIds are set, when the event is activated, and
-- can be used to deactivate the event.
-- The last map is used to unregister propagated events properly
--
type GUIEventReg =  ([Connection],Map Unique [(Unique,GUIEvent)])

--
-- | The event state regarding to gtk
--
newtype GtkRegMap =  GtkRegMap (Map EvtID (Map GUIEventSelector GUIEventReg))

data GuiHandlerStateSel = GuiHandlerStateSel
    deriving (Eq, Ord, Show, Typeable)

instance Selector GuiHandlerStateSel where
    type ValueType GuiHandlerStateSel = Handlers GUIEvent


-- | All gui events share the same map
guiEventFactory :: EventFactory GUIEvent (Handlers GUIEvent)
guiEventFactory = EventFactory {
        efGetHandlers = getState GuiHandlerStateSel,
        efSetHandlers = setState GuiHandlerStateSel}

data GtkEventsStateSel = GtkEventsStateSel
    deriving (Eq, Ord, Show, Typeable)

instance Selector GtkEventsStateSel where
    type ValueType GtkEventsStateSel = GtkRegMap

--getGtkHandlers :: StateM GtkRegMap
--getGtkHandlers = getState GtkEventsStateSel
--
--setGtkHandlers :: GtkRegMap -> StateM ()
--setGtkHandlers = setState GtkEventsStateSel

withGtkHandlers :: (GtkRegMap -> GtkRegMap) -> StateM ()
withGtkHandlers = withState GtkEventsStateSel

--
-- | Constructs a new event. The plugin name has to be unique!
--
makeGUIEvent :: StateM (EventChannel GUIEvent)
makeGUIEvent = do
    let ef           =  guiEventFactory
    ev <- mkEvent (undefined :: GUIEventSelector) ef
    withState GuiHandlerStateSel (\ (Handlers handlerMap :: Handlers GUIEvent) ->
        case Map.lookup (evtID ev) handlerMap of
                Just _ -> error "Events>>makeGUIEvent: Event already known"
                Nothing -> Handlers (Map.insert (evtID ev) [] handlerMap))
    withGtkHandlers (addEventToGtkHandlers ev)
    return ev
  where
    addEventToGtkHandlers ev (GtkRegMap map) =
        case (evtID ev) `Map.lookup` map of
            Nothing -> GtkRegMap $ Map.insert (evtID ev) (Map.empty) map
            Just _ -> error "Events>>makeEvent: Unique not unique"

--
-- | Registers an event handler for this event
--
registerGUIEvent :: GEvent -> [GUIEventSelector] -> Handler GUIEvent -> StateM (HandlerID)
registerGUIEvent event selectors handler = registerEvent event guiEventHandler
  where
    guiEventHandler evt@GUIEvent{geSelector = sel}
        | elem sel selectors    = handler evt
        | otherwise             = return evt

propagateGUIEvent :: GEvent -> [GEvent] -> [GUIEventSelector] -> StateM ()
propagateGUIEvent to fromList selectors =
    mapM_ (\ from -> registerGUIEvent from selectors (\e -> (evtTrigger to) e)) fromList

-- TODO: unregister GUI events

triggerGUIEvent :: GEvent -> GUIEvent -> StateM GUIEvent
triggerGUIEvent event eventValue = (evtTrigger event) eventValue

dummyGUIEvent :: GUIEvent
dummyGUIEvent = GUIEvent{
    geSelector = Dummy,
    geGtkEvent = Gtk.Event True,
    geText = "",
    geMbSelection = Nothing,
    geGtkReturn = True}

--
-- | First register handlers, then activate the GUI event
activateGUIEvent
  :: (GObjectClass o) =>
     o
     -> EventChannel GUIEvent
     -> GUIEventSelector
     -> StateM ()
activateGUIEvent widget event eventSel = do
    activateGUIEvent' widget event (getStandardRegFunction eventSel) eventSel


activateGUIEvent'
  :: (GObjectClass o) =>
     o
     -> EventChannel GUIEvent
     -> (o -> GtkHandler -> IO Connection)
     -> GUIEventSelector
     -> StateM ()
activateGUIEvent' widget event registerFunc eventSel = do
    cid <- reifyState $ \ stateR -> registerFunc widget (\ e -> do
        Handlers handlerMap <- reflectState (getState GuiHandlerStateSel) stateR
        case Map.lookup (evtID event) handlerMap of
                Nothing -> error "Events>>activateGUIEvent: Unknown event"
                Just [] -> return False
                Just handlers -> do
                    name <- if (widget `isA` gTypeWidget)
                                then widgetGetName (castToWidget widget)
                                else return ""
                    eventList <- mapM (\f -> let ev = GUIEvent
                                                    {geSelector    = eventSel,
                                                     geGtkEvent    = e,
                                                     geText        = name,
                                                     geMbSelection = Nothing,
                                                     geGtkReturn   = False}
                                            in reflectState (f ev) stateR)
                                                    (map snd handlers)
                    let boolList = map geGtkReturn eventList
                    return (foldr (&&) True boolList))
    withGtkHandlers (\ (GtkRegMap gtkHandlers) ->
        case Map.lookup (evtID event) gtkHandlers of
                Nothing -> error "Events>>activateGUIEvent: Unknown event"
                Just gtkMap ->
                    GtkRegMap $ Map.insert  (evtID event) (newGtkMap gtkMap cid) gtkHandlers)
  where
    newGtkMap gtkMap cid =
        case Map.lookup eventSel gtkMap of
            Nothing ->  Map.insert eventSel ([cid],Map.empty) gtkMap
            Just (cids,prop) -> Map.insert eventSel (cid:cids,prop) gtkMap

--
-- | A convinence method for not repeating this over and over again
--
getStandardRegFunction :: GUIEventSelector -> GtkRegFunc
getStandardRegFunction FocusOut         =   \w h -> (castToWidget w) `onFocusOut` h
getStandardRegFunction FocusIn          =   \w h -> (castToWidget w) `onFocusIn` h
getStandardRegFunction ButtonPressed    =   \w h -> (castToWidget w) `onButtonPress` h
getStandardRegFunction KeyPressed       =   \w h -> (castToWidget w) `afterKeyRelease` h
getStandardRegFunction Clicked          =   \w h -> liftM castCID $ (castToButton w) `onClicked`
                                                                    (h (Gtk.Event True) >> return ())
getStandardRegFunction _    =   error "Basic>>getStandardRegFunction: no original GUI event"

retriggerAsChanged :: GEvent -> [GUIEventSelector] -> StateM ()
retriggerAsChanged e selList =
    retriggerEvent e (\ ge@GUIEvent{geSelector = sel} ->
        if elem sel selList
            then Just ge{geSelector = MayHaveChanged}
            else Nothing )