content: {-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-}
  
  -----------------------------------------------------------------------------
  -- |
  -- Module      :  XMonad.Layout.MultiColumns
  -- Copyright   :  (c) Anders Engstrom <ankaan@gmail.com>
  -- License     :  BSD3-style (see LICENSE)
  --
  -- Maintainer  :  Anders Engstrom <ankaan@gmail.com>
  -- Stability   :  unstable
  -- Portability :  unportable
  --
  -- This layout tiles windows in a growing number of columns. The number of
  -- windows in each column can be controlled by messages.
  -----------------------------------------------------------------------------
  
  module XMonad.Layout.MultiColumns (
                                -- * Usage
                                -- $usage
  
                                multiCol,
                                MultiCol,
                               ) where
  
  import XMonad
  import qualified XMonad.StackSet as W
  
  import Control.Monad
  
  -- $usage
  -- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@:
  --
  -- > import XMonad.Layout.MultiColumns
  --
  -- Then edit your @layoutHook@ by adding the multiCol layout:
  --
  -- > myLayouts = multiCol [1] 4 0.01 0.5 ||| etc..
  -- > main = xmonad def { layoutHook = myLayouts }
  --
  -- Or alternatively:
  --
  -- > myLayouts = Mirror (multiCol [1] 2 0.01 (-0.25)) ||| etc..
  -- > main = xmonad def { layoutHook = myLayouts }
  --
  -- The maximum number of windows in a column can be controlled using the
  -- IncMasterN messages and the column containing the focused window will be
  -- modified. If the value is 0, all remaining windows will be placed in that
  -- column when all columns before that has been filled.
  --
  -- The size can be set to between 1 and -0.5. If the value is positive, the
  -- master column will be of that size. The rest of the screen is split among
  -- the other columns. But if the size is negative, it instead indicates the
  -- size of all non-master columns and the master column will cover the rest of
  -- the screen. If the master column would become smaller than the other
  -- columns, the screen is instead split equally among all columns. Therefore,
  -- if equal size among all columns are desired, set the size to -0.5.
  --
  -- For more detailed instructions on editing the layoutHook see:
  --
  -- "XMonad.Doc.Extending#Editing_the_layout_hook"
  
  -- | Layout constructor.
  multiCol
    :: [Int]    -- ^ Windows in each column, starting with master. Set to 0 to catch the rest.
    -> Int      -- ^ Default value for all following columns.
    -> Rational -- ^ How much to change size each time.
    -> Rational -- ^ Initial size of master area, or column area if the size is negative.
    -> MultiCol a
  multiCol n defn ds s = MultiCol (map (max 0) n) (max 0 defn) ds s 0
  
  data MultiCol a = MultiCol
    { multiColNWin      :: ![Int]
    , multiColDefWin    :: !Int
    , multiColDeltaSize :: !Rational
    , multiColSize      :: !Rational
    , multiColActive    :: !Int
    } deriving (Show,Read,Eq)
  
  instance LayoutClass MultiCol a where
      doLayout l r s = return (zip w rlist, resl)
          where rlist = doL (multiColNWin l') (multiColSize l') r wlen
                w = W.integrate s
                wlen = length w
                -- Make sure the list of columns is big enough and update active column
                nw = multiColNWin l ++ repeat (multiColDefWin l)
                l' = l { multiColNWin = take (max (length $ multiColNWin l) $ getCol (wlen-1) nw + 1) nw
                       , multiColActive = getCol (length $ W.up s) nw
                       }
                -- Only return new layout if it has been modified
                resl = if l'==l
                       then Nothing
                       else Just l'
      handleMessage l m =
          return $ msum [fmap resize     (fromMessage m)
                        ,fmap incmastern (fromMessage m)]
              where resize Shrink = l { multiColSize = max (-0.5) $ s-ds }
                    resize Expand = l { multiColSize = min 1 $ s+ds }
                    incmastern (IncMasterN x) = l { multiColNWin = take a n ++ [newval] ++ tail r }
                        where newval =  max 0 $ head r + x
                              r = drop a n
                    n = multiColNWin l
                    ds = multiColDeltaSize l
                    s = multiColSize l
                    a = multiColActive l
      description _ = "MultiCol"
  
  
  -- | Get which column a window is in, starting at 0.
  getCol :: Int -> [Int] -> Int
  getCol w (n:ns) = if n<1 || w < n
                    then 0
                    else 1 + getCol (w-n) ns
  -- Should never occur...
  getCol _ _ = -1
  
  doL :: [Int] -> Rational -> Rectangle -> Int -> [Rectangle]
  doL nwin s r n = rlist
      where -- Number of columns to tile
            ncol = getCol (n-1) nwin + 1
            -- Compute the actual size
            size = floor $ abs s * fromIntegral (rect_width r)
            -- Extract all but last column to tile
            c = take (ncol-1) nwin
            -- Compute number of windows in last column and add it to the others
            col = c ++ [n-sum c]
            -- Compute width of columns
            width = if s>0
                    then if ncol==1
                         -- Only one window
                         then [fromIntegral $ rect_width r]
                         -- Give the master it's space and split the rest equally for the other columns
                         else size:replicate (ncol-1) ((fromIntegral (rect_width r) - size) `div` (ncol-1))
                    else if fromIntegral ncol * abs s >= 1
                         -- Split equally
                         then replicate ncol $ fromIntegral (rect_width r) `div` ncol
                         -- Let the master cover what is left...
                         else (fromIntegral (rect_width r) - (ncol-1)*size):replicate (ncol-1) size
            -- Compute the horizontal position of columns
            xpos = accumEx (fromIntegral $ rect_x r) width
            -- Exclusive accumulation
            accumEx a (x:xs) = a:accumEx (a+x) xs
            accumEx _ _ = []
            -- Create a rectangle for each column
            cr = zipWith (\x w -> r { rect_x=fromIntegral x, rect_width=fromIntegral w }) xpos width
            -- Split the columns into the windows
            rlist = concat $ zipWith (\num rect -> splitVertically num rect) col cr
  