------------------------------------------------------------------------------
-- |
-- Module      :  Pager
-- Copyright   :  (c) Mads N Noe 2009
-- Maintainer  :  mntnoe (@) gmail.com
-- License     :  as-is
--
-- A pager for DynamicLog showing a symbol for each window on each workspace.
--
------------------------------------------------------------------------------

module Pager (
    -- * Usage
    -- $usage

    labeledPager
  ) where

-- XMonad modules
import XMonad
import Data.Maybe ( isJust, fromMaybe )
import qualified Data.Map as M
import Data.Map ( (!) )
import Data.List
import qualified XMonad.StackSet as W
import XMonad.Hooks.DynamicLog
import XMonad.Hooks.UrgencyHook

-- My modules
import Util

-- $usage
--
-- The simplest way to use this module is to add something like this in your
-- @~\/.xmonad\/xmonad.hs@.  Note that you need to patch your xmonad source to
-- allow support for user modules (see my @xmonad.hs@).
--
-- > import XMonad.Hooks.DynamicLog
-- > import Pager
-- >
-- > main = xmonad $ defaultConfig {
-- >    ...
-- >    logHook = myDynamicLog
-- >    ...
-- >  }
-- >
-- > myDynamicLog :: PP
-- > myDynamicLog = defaultPP
-- >     { ppOrder           = order
-- >     , ppExtras          = [ labeledPager myDynamicLog windowLabelMap ]
-- >     }
-- >   where
-- >     order (_:l:t:ws:_) = ws:l:t:[]
-- >     order xs           = ["Error in order list: " ++ show xs]
--
-- You also need a way to assign symbols to your windows. Here is a simple
-- example using single letter symbols, but you also use dzen icons.
--
-- > -- | Map windows to symbols for the pager.  Symbols for floating windows are in
-- > --   lower case.
-- > windowLabelMap :: [(String, Query Bool)]
-- > windowLabelMap =
-- >     map whenFloat tiledWindows ++ tiledWindows
-- >     ++
-- >     map whenFloat generalQueries ++ generalQueries
-- >   where
-- >
-- >     whenFloat (l, q) = (map toLower l, isFloat <&&> q)
-- >
-- >     tiledWindows =
-- >         [ ("V", className =? "Gvim")
-- >         , ("E", className =? "Emacs")
-- >         , ("W", className =? "Firefox")
-- >         ]
-- >
-- >     generalQueries =
-- >         [ ("T", appName =? "xterm")
-- >         , ("X", return True) -- catchall
-- >         ]

-- | The 'DynamicLog' logger to add to 'ppExtras' using the given pretty
--   printer and window label map.
labeledPager :: PP -> [(String, Query Bool)] -> X (Maybe String)
labeledPager pp lm = do
    s       <- gets windowset
    urgents <- readUrgents
    sort'   <- ppSort pp
    wl      <- queryWindows s lm
    return $ Just $ pprWindowSet' sort' urgents wl pp s

-- | like 'pprWindowSet', but append to each workspace the outcome of
--   'printWindows'.
pprWindowSet' :: ([W.Workspace String l Window] -> [W.Workspace String l Window])
                                                   -- ^ sorting function
              -> [Window]                          -- ^ urgent windows
              -> M.Map Window String               -- ^ window to symbol map
              -> PP                                -- ^ pretty-Printer
              -> W.StackSet String l Window sid sd -- ^ stack set
              -> String
pprWindowSet' sort' urgents wl pp s 
    = sepBy (ppWsSep pp) . map fmt . sort' $
            map W.workspace (W.current s : W.visible s) ++ W.hidden s
   where this     = W.tag (W.workspace (W.current s))
         visibles = map (W.tag . W.workspace) (W.visible s)

         fmt ws = printer pp (W.tag ws ++ printWindows wl (W.integrate' $ W.stack ws))
          where printer | W.tag ws == this         = ppCurrent
                        | W.tag ws `elem` visibles = ppVisible
                        | any (\-> maybe False (== W.tag ws) (W.findTag x s)) urgents  
                                                   = \ppC -> ppUrgent ppC . ppHidden ppC
                        | isJust (W.stack ws)      = ppHidden
                        | otherwise                = ppHiddenNoWindows

-- | Output a list of strings, ignoring empty ones and separating the
--   rest with the given separator.
sepBy :: String   -- ^ separator
      -> [String] -- ^ fields to output
      -> String
sepBy sep = concat . intersperse sep . filter (not . null)

-- | Print a concatenated string of symbols for a list of windows.
printWindows :: M.Map Window String -- ^ window to symbol map
             -> [Window]            -- ^ windows on the workspace
             -> String
printWindows wl ws = pad $ concat $ map (\-> fromMaybe "" $ M.lookup w wl) ws
  where
    pad "" = ""
    pad xs = ":"++xs

-- | Query each window in the 'WindowSet' and assign a symbol to it in a map.
queryWindows :: WindowSet -> [(String, Query Bool)] -> X (M.Map Window String)
queryWindows ws lm = do
    mapM (qw lm) (W.allWindows ws) >>= return . M.fromList
  where
    qw :: [(String, Query Bool)] -> Window -> X (Window, String)
    qw [] w           = return (w, "?")
    qw ((l, q):lqs) w = runQuery q w >>= if_ (return (w, l)) (qw lqs w)