    {-# LANGUAGE TypeSynonymInstances, MultiParamTypeClasses, DeriveDataTypeable, PatternGuards #-}

    -----------------------------------------------------------------------------
    -- |
    -- Module      :  XMonad.Layout.Monitor
    -- Copyright   :  (c) Roman Cheplyaka
    -- License     :  BSD-style (see LICENSE)
    --
    -- Maintainer  :  Roman Cheplyaka <roma@ro-che.info>
    -- Stability   :  unstable
    -- Portability :  unportable
    --
    -- Layout modfier for displaying some window (monitor) above other windows
    --
    -----------------------------------------------------------------------------
    module XMonad.Layout.Monitor (
        -- * Usage
        -- $usage

        -- * Hints and issues
        -- $hints

        Monitor(..),
        monitor,
        Property(..),
        MonitorMessage(..),
        doHideIgnore,
        manageMonitor

        -- * TODO
        -- $todo
        ) where

    import XMonad
    import XMonad.Layout.LayoutModifier
    import XMonad.Util.WindowProperties
    import XMonad.Hooks.ManageHelpers (doHideIgnore)
    import XMonad.Hooks.FadeInactive (setOpacity)
    import Control.Monad

    -- $usage
    -- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@:
    --
    -- > import XMonad.Layout.Monitor
    --
    -- Define 'Monitor' record. 'monitor' can be used as a template. At least 'prop'
    -- and 'rect' should be set here. Also consider setting 'persistent' to True.
    --
    -- Minimal example:
    --
    -- > myMonitor = monitor
    -- >     { prop = ClassName "SomeClass"
    -- >     , rect = Rectangle 0 0 40 20 -- rectangle 40x20 in upper left corner
    -- >     }
    --
    -- More interesting example:
    --
    -- > clock = monitor {
    -- >      -- Cairo-clock creates 2 windows with the same classname, thus also using title
    -- >      prop = ClassName "Cairo-clock" `And` Title "MacSlow's Cairo-Clock"
    -- >      -- rectangle 150x150 in lower right corner, assuming 1280x800 resolution
    -- >    , rect = Rectangle (1280-150) (800-150) 150 150
    -- >      -- avoid flickering
    -- >    , persistent = True
    -- >      -- make the window transparent
    -- >    , opacity = 0.6
    -- >      -- hide on start
    -- >    , visible = False
    -- >      -- assign it a name to be able to toggle it independently of others
    -- >    , name = "clock"
    -- >    }
    --
    -- Add ManageHook to de-manage monitor windows and apply opacity settings.
    --
    -- > manageHook = myManageHook <+> manageMonitor clock
    --
    -- Apply layout modifier.
    --
    -- > myLayout = ModifiedLayout clock $ tall ||| Full ||| ...
    --
    -- After that, if there exists a window with specified properties, it will be
    -- displayed on top of all /tiled/ (not floated) windows on specified
    -- position.
    --
    -- It's also useful to add some keybinding to toggle monitor visibility:
    --
    -- > , ((mod1Mask, xK_u     ), broadcastMessage ToggleMonitor >> refresh)
    --
    -- Screenshot: <http://www.haskell.org/haskellwiki/Image:Xmonad-clock.png>

    data Monitor a = Monitor
        { prop :: Property    -- ^ property which uniquely identifies monitor window
        , rect :: Rectangle   -- ^ specifies where to put monitor
        , visible :: Bool     -- ^ is it visible by default?
        , name :: String      -- ^ name of monitor (useful when we have many of them)
        , persistent :: Bool  -- ^ is it shown on all layouts?
        , opacity :: Rational -- ^ opacity level
        } deriving (Read, Show)

    -- | Template for 'Monitor' record. At least 'prop' and 'rect' should be
    -- redefined. Default settings: 'visible' is 'True', 'persistent' is 'False'.
    monitor :: Monitor a
    monitor = Monitor
        { prop = Const False
        , rect = Rectangle 0 0 0 0
        , visible = True
        , name = ""
        , persistent = False
        , opacity = 1
        }

    -- | Messages without names affect all monitors. Messages with names affect only
    -- monitors whose names match.
    data MonitorMessage = ToggleMonitor | ShowMonitor | HideMonitor
                        | ToggleMonitorNamed String
                        | ShowMonitorNamed String
                        | HideMonitorNamed String
        deriving (Read,Show,Eq,Typeable)
    instance Message MonitorMessage

    withMonitor :: Property -> a -> (Window -> X a) -> X a
    withMonitor p a fn = do
        monitorWindows <- allWithProperty p
        case monitorWindows of
            [] -> return a
            w:_ -> fn w

    instance LayoutModifier Monitor Window where
        redoLayout mon _ _ rects = withMonitor (prop mon) (rects, Nothing) $ \w ->
            if visible mon
                then do tileWindow w (rect mon)
                        reveal w
                        return ((w,rect mon):rects, Nothing)
                else do hide w
                        return (rects, Nothing)
        handleMess mon mess
            | Just ToggleMonitor <- fromMessage mess = return $ Just $ mon { visible = not $ visible mon }
            | Just (ToggleMonitorNamed n) <- fromMessage mess = return $
                if name mon == n then Just $ mon { visible = not $ visible mon } else Nothing
            | Just ShowMonitor <- fromMessage mess = return $ Just $ mon { visible = True }
            | Just (ShowMonitorNamed n) <- fromMessage mess = return $
                if name mon == n then Just $ mon { visible = True } else Nothing
            | Just HideMonitor <- fromMessage mess = return $ Just $ mon { visible = False }
            | Just (HideMonitorNamed n) <- fromMessage mess = return $
                if name mon == n then Just $ mon { visible = False } else Nothing
            | Just Hide <- fromMessage mess = do unless (persistent mon) $ withMonitor (prop mon) () hide; return Nothing
            | otherwise = return Nothing

    -- | ManageHook which demanages monitor window and applies opacity settings.
    manageMonitor :: Monitor a -> ManageHook
    manageMonitor mon = propertyToQuery (prop mon) --> do
        w <- ask
        liftX $ setOpacity w $ opacity mon
        if persistent mon then doIgnore else doHideIgnore

    -- $hints
    -- - This module assumes that there is only one window satisfying property exists.
    --
    -- - If your monitor is available on /all/ layouts, set
    -- 'persistent' to 'True' to avoid unnecessary
    -- flickering. You can still toggle monitor with a keybinding.
    --
    -- - You can use several monitors with nested modifiers. Give them names
    ---  to be able to toggle them independently.
    --
    -- - You can display monitor only on specific workspaces with
    -- "XMonad.Layout.PerWorkspace".

    -- $todo
    -- - make Monitor remember the window it manages
    --
    -- - specify position relative to the screen