content: {-# LANGUAGE FlexibleContexts, FlexibleInstances, MultiParamTypeClasses, UndecidableInstances, PatternGuards, DeriveDataTypeable #-}
  -----------------------------------------------------------------------------
  -- |
  -- Module      :  XMonad.Layout.LayoutBuilder
  -- Copyright   :  (c) 2009 Anders Engstrom <ankaan@gmail.com>
  -- License     :  BSD3-style (see LICENSE)
  --
  -- Maintainer  :  Anders Engstrom <ankaan@gmail.com>
  -- Stability   :  unstable
  -- Portability :  unportable
  --
  -- A layout combinator that sends a specified number of windows to one rectangle
  -- and the rest to another. Each of these rectangles are given a layout that
  -- is used within them. This can be chained to provide an arbitrary number of
  -- rectangles. The layout combinator allows overlapping rectangles, but such
  -- layouts does not work well together with hinting
  -- ("XMonad.Layout.LayoutHints", "XMonad.Layout.HintedGrid" etc.)
  --
  -----------------------------------------------------------------------------
  
  module XMonad.Layout.LayoutBuilder (
    -- * Usage
    -- $usage
    layoutN,
    layoutR,
    layoutAll,
    IncLayoutN (..),
    SubMeasure (..),
    SubBox (..),
    absBox,
    relBox,
    LayoutN,
  ) where
  
  import XMonad
  import qualified XMonad.StackSet as W
  import Data.Maybe (isJust,isNothing,listToMaybe)
  
  -- $usage
  -- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@:
  --
  -- > import XMonad.Layout.LayoutBuilder
  --
  -- Then edit your @layoutHook@ by adding something like:
  --
  -- > myLayout = ( (layoutN 1 (relBox 0 0 0.5 1) (Just $ relBox 0 0 1 1) $ simpleTabbed)
  -- >             $ (layoutAll (relBox 0.5 0 1 1)                         $ simpleTabbed)
  -- >             ) |||
  -- >             ( (layoutN 1       (relBox (1/3) 0 (1/2) 1) (Just $ relBox 0 0 1 1) $ Tall 0 0.01 0.5)
  -- >             $ (layoutR 0.1 0.5 (relBox (2/3) 0 1     1) Nothing                 $ Tall 0 0.01 0.5)
  -- >             $ (layoutAll       (relBox 0     0 (1/3) 1)                         $ Tall 0 0.01 0.5)
  -- >             ) |||
  -- >             ( (layoutN 1 (absBox (-512-200) 0 512        0) (Just $ relBox 0 0 1 1) $ simpleTabbed)
  -- >             $ (layoutN 1 (absBox (-200)     0 0          0) Nothing                 $ simpleTabbed)
  -- >             $ (layoutAll (absBox 0          0 (-512-200) 0)                         $ simpleTabbed)
  -- >             ) |||
  -- >             ( (layoutN 1 (absBox 10 0 0 (-10)) Nothing $ Tall 0 0.01 0.5)
  -- >             $ (layoutN 1 (absBox 0 0 200 0) Nothing $ Tall 0 0.01 0.5)
  -- >             $ (layoutAll (absBox 10 10 0 0) $ Tall 2 0.01 0.5)
  -- >             ) ||| Full ||| etc...
  -- > main = xmonad def { layoutHook = myLayout }
  --
  -- This will produce a layout similar to DragPane, but with the possibility to have multiple windows in the left half
  -- and tabs that show the available windows. It will also produce a layout similar to ThreeColMid and a special layout
  -- created for use with a 80 columns wide Emacs window, its sidebar and a tabbed area for all other windows.
  --
  -- The final layout is for applications that use a toolbar in a separate window, shown on a low resolution screen. It has
  -- a master area that cover almost the whole screen. It leaves 10 px to the left and 10 px at the bottom. To the left
  -- the toolbar is located and can be accessed by focusing this area. It is actually 200 px wide, but usually below the
  -- other windows. Similarly all other windows are tiled, but behind the master window and can be accessed by moving the
  -- mouse to the bottom of the screen. Everything can also be accessed by the standard focus changing key bindings.
  --
  -- This module can be used to create many different custom layouts, but there are limitations. The primary limitation
  -- can be observed in the second and third example when there are only two columns with windows in them. The leftmost
  -- area is left blank. These blank areas can be avoided by placing the rectangles appropriately.
  --
  -- These examples require "XMonad.Layout.Tabbed".
  --
  -- For more detailed instructions on editing the layoutHook see:
  --
  -- "XMonad.Doc.Extending#Editing_the_layout_hook"
  --
  -- You may wish to add the following keybindings:
  --
  -- >    , ((modm .|. shiftMask, xK_h ), sendMessage $ IncLayoutN (-1))
  -- >    , ((modm .|. shiftMask, xK_l ), sendMessage $ IncLayoutN 1)
  --
  -- For detailed instruction on editing the key binding see:
  --
  -- "XMonad.Doc.Extending#Editing_key_bindings".
  
  type WindowNum = Either Int (Rational,Rational)
  
  -- | Use one layout in the specified area for a number of windows and possibly let another layout handle the rest.
  data LayoutN l1 l2 a =
      LayoutN (Maybe a) (Maybe a) WindowNum SubBox (Maybe SubBox) (l1 a) (Maybe (l2 a))
      deriving (Show,Read)
  
  -- | Use the specified layout in the described area for N windows and send the rest of the windows to the next layout in the chain.
  --   It is possible to supply an alternative area that will then be used instead, if there are no windows to send to the next layout.
  layoutN :: (Read a, Eq a, LayoutClass l1 a, LayoutClass l2 a, LayoutClass l3 a) =>
         Int                          -- ^ The number of windows to handle
      -> SubBox                       -- ^ The box to place the windows in
      -> Maybe SubBox                 -- ^ Possibly an alternative box that is used when this layout handles all windows that are left
      -> l1 a                         -- ^ The layout to use in the specified area
      -> LayoutN l2 l3 a              -- ^ Where to send the remaining windows
      -> LayoutN l1 (LayoutN l2 l3) a -- ^ The resulting layout
  layoutN num box mbox sub next = LayoutN Nothing Nothing (Left num) box mbox sub (Just next)
  
  -- | As layoutN, but the number of windows is given relative to the total number of windows remaining to be handled. The first
  --   argument is how much to change the ratio when using IncLayoutN, and the second is the initial ratio.
  layoutR :: (Read a, Eq a, LayoutClass l1 a, LayoutClass l2 a, LayoutClass l3 a) =>
         Rational                     -- ^ How much to change the ratio with each IncLayoutN
      -> Rational                     -- ^ The ratio of the remaining windows to handle
      -> SubBox                       -- ^ The box to place the windows in
      -> Maybe SubBox                 -- ^ Possibly an alternative box that is used when this layout handles all windows that are left
      -> l1 a                         -- ^ The layout to use in the specified area
      -> LayoutN l2 l3 a              -- ^ Where to send the remaining windows
      -> LayoutN l1 (LayoutN l2 l3) a -- ^ The resulting layout
  layoutR numdiff num box mbox sub next = LayoutN Nothing Nothing (Right (numdiff,num)) box mbox sub (Just next)
  
  -- | Use the specified layout in the described area for all remaining windows.
  layoutAll :: (Read a, Eq a, LayoutClass l1 a) =>
         SubBox             -- ^ The box to place the windows in
      -> l1 a               -- ^ The layout to use in the specified area
      -> LayoutN l1 Full a  -- ^ The resulting layout
  layoutAll box sub = LayoutN Nothing Nothing (Right (0,1)) box Nothing sub Nothing
  
  -- | Change the number of windows handled by the focused layout.
  data IncLayoutN = IncLayoutN Int deriving Typeable
  instance Message IncLayoutN
  
  -- | The absolute or relative measures used to describe the area a layout should be placed in. For negative absolute values
  --   the total remaining space will be added. For sizes, the remaining space will also be added for zeroes. Relative values
  --   are applied on the remaining space after the top-left corner of the box have been removed.
  data SubMeasure = Abs Int | Rel Rational deriving (Show,Read)
  
  -- | A box to place a layout in. The stored values are xpos, ypos, width and height.
  data SubBox = SubBox SubMeasure SubMeasure SubMeasure SubMeasure deriving (Show,Read)
  
  
  -- | Create a box with only absolute measurements. If the values are negative, the total remaining space will be added. For
  --   sizes it will also be added for zeroes.
  absBox :: Int     -- ^ Absolute X-Position
         -> Int     -- ^ Absolute Y-Position
         -> Int     -- ^ Absolute width
         -> Int     -- ^ Absolute height
         -> SubBox  -- ^ The resulting 'SubBox' describing the area
  absBox x y w h = SubBox (Abs x) (Abs y) (Abs w) (Abs h)
  
  
  -- | Create a box with only relative measurements.
  relBox :: Rational  -- ^ Relative X-Position with respect to the surrounding area
         -> Rational  -- ^ Relative Y-Position with respect to the surrounding area
         -> Rational  -- ^ Relative width with respect to the remaining width
         -> Rational  -- ^ Relative height with respect to the remaining height
         -> SubBox    -- ^ The resulting 'SubBox' describing the area
  relBox x y w h = SubBox (Rel x) (Rel y) (Rel w) (Rel h)
  
  
  instance (LayoutClass l1 a, LayoutClass l2 a, Read a, Show a, Eq a, Typeable a) =>
      LayoutClass (LayoutN l1 l2) a where
  
          -- | Update window locations.
          runLayout (W.Workspace _ (LayoutN subf nextf num box mbox sub next) s) rect
              = do let (subs,nexts,subf',nextf') = splitStack s num subf nextf
                       selBox = if isJust nextf'
                                then box
                                else maybe box id mbox
  
                   (sublist,sub',schange) <- handle sub subs $ calcArea selBox rect
  
                   (nextlist,next',nchange) <- case next of Nothing -> return ([], Nothing, False)
                                                            Just n -> do (res, l, ch) <- handle n nexts rect
                                                                         return (res, Just l, ch)
  
                   let newlist =  if (length $ maybe [] W.up s) < (length $ W.integrate' subs)
                                  then sublist++nextlist
                                  else nextlist++sublist
                       newstate = if subf' /= subf || nextf' /= nextf || schange || nchange
                                  then Just $ LayoutN subf' nextf' num box mbox sub' next'
                                  else Nothing
  
                   return (newlist, newstate)
                where
                    handle l s' r = do (res,ml) <- runLayout (W.Workspace "" l s') r
                                       l' <- return $ maybe l id ml
                                       return (res, l', isNothing ml)
  
          -- |  Propagate messages.
          handleMessage l m
              | Just (IncLayoutN _) <- fromMessage m = windowNum l m
              | Just (IncMasterN _) <- fromMessage m = sendFocus l m
              | Just (Shrink) <- fromMessage m = sendFocus l m
              | Just (Expand) <- fromMessage m = sendFocus l m
              | otherwise = sendBoth l m
  
          -- |  Descriptive name for layout.
          description (LayoutN _ _ _ _ _ sub Nothing) = "layoutAll "++ description sub
          description (LayoutN _ _ (Left _) _ _ sub (Just next)) = "layoutN "++ description sub ++" "++ description next
          description (LayoutN _ _ (Right _) _ _ sub (Just next)) = "layoutR "++ description sub ++" "++ description next
  
  
  windowNum :: (LayoutClass l1 a, LayoutClass l2 a, Read a, Show a, Eq a, Typeable a) => LayoutN l1 l2 a -> SomeMessage -> X (Maybe (LayoutN l1 l2 a))
  windowNum l@(LayoutN subf nextf num box mbox subl nextl) m | (Just (IncLayoutN n)) <- fromMessage m =
      do foc <- isFocus subf
         if foc then do let newnum = case num of
                                         (Left oldnum) -> Left $ max 1 $ oldnum + n
                                         (Right (diff,oldnum)) -> Right (diff, min 1 $ max 0 $ oldnum + (fromIntegral n)*diff)
                        return $ Just $ LayoutN subf nextf newnum box mbox subl nextl
                else sendNext l m
  windowNum l m = sendNext l m
  
  sendSub :: (LayoutClass l1 a, LayoutClass l2 a, Read a, Show a, Eq a, Typeable a) => LayoutN l1 l2 a -> SomeMessage -> X (Maybe (LayoutN l1 l2 a))
  sendSub (LayoutN subf nextf num box mbox sub next) m =
      do sub' <- handleMessage sub m
         return $ if isJust sub'
                  then Just $ LayoutN subf nextf num box mbox (maybe sub id sub') next
                  else Nothing
  
  sendBoth :: (LayoutClass l1 a, LayoutClass l2 a, Read a, Show a, Eq a, Typeable a) => LayoutN l1 l2 a -> SomeMessage -> X (Maybe (LayoutN l1 l2 a))
  sendBoth l@(LayoutN _ _ _ _ _ _ Nothing) m = sendSub l m
  sendBoth (LayoutN subf nextf num box mbox sub (Just next)) m =
      do sub' <- handleMessage sub m
         next' <- handleMessage next m
         return $ if isJust sub' || isJust next'
                  then Just $ LayoutN subf nextf num box mbox (maybe sub id sub') (Just $ maybe next id next')
                  else Nothing
  
  sendNext :: (LayoutClass l1 a, LayoutClass l2 a, Read a, Show a, Eq a, Typeable a) => LayoutN l1 l2 a -> SomeMessage -> X (Maybe (LayoutN l1 l2 a))
  sendNext (LayoutN _ _ _ _ _ _ Nothing) _ = return Nothing
  sendNext (LayoutN subf nextf num box mbox sub (Just next)) m =
      do next' <- handleMessage next m
         return $ if isJust next'
                  then Just $ LayoutN subf nextf num box mbox sub next'
                  else Nothing
  
  sendFocus :: (LayoutClass l1 a, LayoutClass l2 a, Read a, Show a, Eq a, Typeable a) => LayoutN l1 l2 a -> SomeMessage -> X (Maybe (LayoutN l1 l2 a))
  sendFocus l@(LayoutN subf _ _ _ _ _ _) m = do foc <- isFocus subf
                                                if foc then sendSub l m
                                                       else sendNext l m
  
  isFocus :: (Show a) => Maybe a -> X Bool
  isFocus Nothing = return False
  isFocus (Just w) = do ms <- (W.stack . W.workspace . W.current) `fmap` gets windowset
                        return $ maybe False (\s -> show w == (show $ W.focus s)) ms
  
  
  calcNum :: Int -> WindowNum -> Int
  calcNum tot num = max 1 $ case num of Left i -> i
                                        Right (_,r) -> ceiling $ r * fromIntegral tot
  
  splitStack :: Eq a => Maybe (W.Stack a) -> WindowNum -> Maybe a -> Maybe a -> (Maybe (W.Stack a),Maybe (W.Stack a),Maybe a,Maybe a)
  splitStack Nothing _ _ _ = (Nothing,Nothing,Nothing,Nothing)
  splitStack (Just s) num subf nextf = ( differentiate' subf' subl
                                       , differentiate' nextf' nextl
                                       , subf'
                                       , nextf'
                                       )
      where
          ws = W.integrate s
          n = calcNum (length ws) num
          subl = take n ws
          nextl = drop n ws
          subf' = foc subl subf
          nextf' = foc nextl nextf
          foc [] _ = Nothing
          foc l f | W.focus s `elem` l = Just $ W.focus s
                  | maybe False (`elem` l) f = f
                  | otherwise = listToMaybe l
  
  calcArea :: SubBox -> Rectangle -> Rectangle
  calcArea (SubBox xpos ypos width height) rect = Rectangle (rect_x rect + fromIntegral xpos') (rect_y rect + fromIntegral ypos') width' height'
      where
          xpos' = calc False xpos $ rect_width rect
          ypos' = calc False ypos $ rect_height rect
          width' = calc True width $ rect_width rect - xpos'
          height' = calc True height $ rect_height rect - ypos'
  
          calc zneg val tot = fromIntegral $ min (fromIntegral tot) $ max 0 $
              case val of Rel v -> floor $ v * fromIntegral tot
                          Abs v -> if v<0 || (zneg && v==0)
                                   then (fromIntegral tot)+v
                                   else v
  
  differentiate' :: Eq q => Maybe q -> [q] -> Maybe (W.Stack q)
  differentiate' _ [] = Nothing
  differentiate' Nothing w = W.differentiate w
  differentiate' (Just f) w
      | f `elem` w = Just $ W.Stack { W.focus = f
                                    , W.up    = reverse $ takeWhile (/=f) w
                                    , W.down  = tail $ dropWhile (/=f) w
                                    }
      | otherwise = W.differentiate w
  