content: {-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, TypeSynonymInstances, FlexibleContexts #-}
  
  -----------------------------------------------------------------------------
  -- |
  -- Module      :  XMonad.Layout.IM
  -- Copyright   :  (c) Roman Cheplyaka, Ivan N. Veselov <veselov@gmail.com>
  -- License     :  BSD-style (see LICENSE)
  --
  -- Maintainer  :  Roman Cheplyaka <roma@ro-che.info>
  -- Stability   :  unstable
  -- Portability :  unportable
  --
  -- Layout modfier suitable for workspace with multi-windowed instant messenger
  -- (like Psi or Tkabber).
  --
  -----------------------------------------------------------------------------
  
  module XMonad.Layout.IM (
      -- * Usage
      -- $usage
  
      -- * Hints
      -- $hints
  
      -- * TODO
      -- $todo
          Property(..), IM(..), withIM, gridIM,
          AddRoster,
  ) where
  
  import XMonad
  import qualified XMonad.StackSet as S
  import XMonad.Layout.Grid
  import XMonad.Layout.LayoutModifier
  import XMonad.Util.WindowProperties
  
  -- $usage
  -- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@:
  --
  -- > import XMonad.Layout.IM
  -- > import Data.Ratio ((%))
  --
  -- Then edit your @layoutHook@ by adding IM modifier to layout which you prefer
  -- for managing your chat windows (Grid in this example, another useful choice
  -- to consider is Tabbed layout).
  --
  -- > myLayout = withIM (1%7) (ClassName "Tkabber") Grid ||| Full ||| etc..
  -- > main = xmonad def { layoutHook = myLayout }
  --
  -- Here @1%7@ is the part of the screen which your roster will occupy,
  -- @ClassName \"Tkabber\"@ tells xmonad which window is actually your roster.
  --
  -- Screenshot: <http://haskell.org/haskellwiki/Image:Xmonad-layout-im.png>
  --
  -- For more detailed instructions on editing the layoutHook see:
  --
  -- "XMonad.Doc.Extending#Editing_the_layout_hook"
  
  -- $hints
  --
  -- To launch IM layout automatically on your IM workspace use "XMonad.Layout.PerWorkspace".
  --
  -- By default the roster window will appear on the left side.
  -- To place roster window on the right side, use @reflectHoriz@ from
  -- "XMonad.Layout.Reflect" module.
  
  -- $todo
  -- This item are questionable. Please let me know if you find them useful.
  --
  -- * shrink\/expand
  --
  
  -- | Data type for LayoutModifier which converts given layout to IM-layout
  -- (with dedicated space for the roster and original layout for chat windows)
  data AddRoster a = AddRoster Rational Property deriving (Read, Show)
  
  instance LayoutModifier AddRoster Window where
    modifyLayout (AddRoster ratio prop) = applyIM ratio prop
    modifierDescription _                = "IM"
  
  -- | Modifier which converts given layout to IM-layout (with dedicated
  -- space for roster and original layout for chat windows)
  withIM :: LayoutClass l a => Rational -> Property -> l a -> ModifiedLayout AddRoster l a
  withIM ratio prop = ModifiedLayout $ AddRoster ratio prop
  
  -- | IM layout modifier applied to the Grid layout
  gridIM :: Rational -> Property -> ModifiedLayout AddRoster Grid a
  gridIM ratio prop = withIM ratio prop Grid
  
  -- | Internal function for adding space for the roster specified by
  -- the property and running original layout for all chat windows
  applyIM :: (LayoutClass l Window) =>
                 Rational
              -> Property
              -> S.Workspace WorkspaceId (l Window) Window
              -> Rectangle
              -> X ([(Window, Rectangle)], Maybe (l Window))
  applyIM ratio prop wksp rect = do
      let stack = S.stack wksp
      let ws = S.integrate' $ stack
      let (masterRect, slaveRect) = splitHorizontallyBy ratio rect
      master <- findM (hasProperty prop) ws
      case master of
          Just w -> do
              let filteredStack = stack >>= S.filter (w /=)
              wrs <- runLayout (wksp {S.stack = filteredStack}) slaveRect
              return ((w, masterRect) : fst wrs, snd wrs)
          Nothing -> runLayout wksp rect
  
  -- | Like find, but works with monadic computation instead of pure function.
  findM :: Monad m => (a -> m Bool) -> [a] -> m (Maybe a)
  findM _ [] = return Nothing
  findM f (x:xs) = do { b <- f x; if b then return (Just x) else findM f xs }
  
  -- | This is for compatibility with old configs only and will be removed in future versions!
  data IM a = IM Rational Property deriving (Read, Show)
  instance LayoutClass IM Window where
      description _ = "IM"
      doLayout (IM r prop) rect stack = do
          let ws = S.integrate stack
          let (masterRect, slaveRect) = splitHorizontallyBy r rect
          master <- findM (hasProperty prop) ws
          let positions = case master of
                  Just w -> (w, masterRect) : arrange defaultRatio slaveRect (filter (w /=) ws)
                  Nothing -> arrange defaultRatio rect ws
          return (positions, Nothing)
  