{-# 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