{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, DeriveDataTypeable #-}

-----------------------------------------------------------------------------
-- |
-- Module      :  XMonad.Layout.MessageControl
-- Copyright   :  (c) 2008 Quentin Moser
-- License     :  BSD3
--
-- Maintainer  :  <quentin.moser@unifr.ch>
-- Stability   :  unstable
-- Portability :  unportable
--
-- Provides message \"escaping\" and filtering facilities which
-- help control complex nested layouts.
-----------------------------------------------------------------------------

module XMonad.Layout.MessageControl (
                               -- * Usage
                               -- $usage
                            Ignore()
                          , ignore
                          , UnEscape()
                          , unEscape
                          , EscapedMessage(Escape)
                          , escape
                          ) where

import XMonad.Core (Message, SomeMessage(..)
                   , fromMessage, LayoutClass(..))
import XMonad.StackSet (Workspace(..))

import XMonad.Layout.LayoutModifier (LayoutModifier(..), ModifiedLayout(..))

import Data.Typeable (Typeable)
import Control.Applicative ((<$>))
import Control.Arrow (second)

-- $usage
-- You can use this module by importing it into your @~\/.xmonad\/xmonad.hs@ file:
--
-- > import XMonad.Layout.MessageEscape
--
-- Then, if you use a modified layout where the modifier would intercept
-- a message, but you'd want to be able to send it to the inner layout 
-- only, add the 'unEscape' modifier to the inner layout like so:
--
-- > import XMonad.Layout.Master (mastered)
-- > import XMonad.Layout.Tabbed (simpleTabbed)
-- > import XMonad.Layout.LayoutCombinators ((|||))
-- >
-- > myLayout = Tall ||| unEscape (mastered 0.01 0.5 $ Full ||| simpleTabbed)
--
-- you can now send a message to the inner layout with
--  @sendMessage $ escape message@, e.g.
--
-- > -- Change the inner layout
-- > ((modm .|. controlMask, xK_space), sendMessage $ escape NextLayout)
--
-- If you want unescaped messages to be handled /only/ by the enclosing
-- layout, use the 'ignore' modifier:
--
-- > myLayout = Tall ||| (ignore NextLayout $ ignore (JumpToLayout "") $
-- >                       unEscape $ mastered 0.01 0.5 
-- >                         $ Full ||| simpleTabbed)
--
-- /IMPORTANT NOTE:/ The standard '(|||)' operator from "XMonad.Layout"
-- does not behave correctly with 'ignore'. Make sure you use the one
-- from "XMonad.Layout.LayoutCombinators".

-- | the Ignore layout modifier. Prevents its inner layout from receiving
-- messages of a certain type.

data Ignore m l w = I (l w)
                    deriving (Show, Read)

instance (Message m, LayoutClass l w) => LayoutClass (Ignore m l) w where
    runLayout ws r = second (I <$>) <$> runLayout (unILayout ws) r
        where  unILayout :: Workspace i (Ignore m l w) w -> Workspace i (l w) w
               unILayout w@(Workspace { layout = (I l) }) = w { layout = l }
    handleMessage l@(I l') sm
        = case fromMessageAs sm l of
            Just _ -> return Nothing
            Nothing -> (I <$>) <$> handleMessage l' sm
        where fromMessageAs :: Message m' => SomeMessage -> Ignore m' l w -> Maybe m'
              fromMessageAs a _ = fromMessage a
    description (I l) = "Ignore "++description l

-- | the UnEscape layout modifier. Listens to 'EscapedMessage's and sends
-- their nested message to the inner layout.

data UnEscape w = UE
                deriving (Show, Read)

instance LayoutModifier UnEscape a where
    handleMessOrMaybeModifyIt _ mess
        = return $ case fromMessage mess of
                     Just (Escape mess') -> Just $ Right mess'
                     Nothing -> Nothing


-- | Data type for an escaped message. Send with 'escape'.

newtype EscapedMessage = Escape SomeMessage
    deriving Typeable

instance Message EscapedMessage


-- | Creates an 'EscapedMessage'.

escape :: Message m => m -> EscapedMessage
escape = Escape . SomeMessage


-- | Applies the UnEscape layout modifier to a layout.

unEscape :: LayoutClass l w => l w -> ModifiedLayout UnEscape l w
unEscape l = ModifiedLayout UE l


-- | Applies the Ignore layout modifier to a layout, blocking
-- all messages of the same type as the one passed as its first argument.

ignore :: (Message m, LayoutClass l w) 
          => m -> l w -> (Ignore m l w)
ignore _ l = I l