------------------------------------------------------------------------------
-- |
-- Module      :  Pager
-- Copyright   :  (c) Mads N Noe 2010
-- Maintainer  :  mail (@) madsnoe.dk
-- License     :  as-is
--
-- A pager for DynamicLog showing an for each window on each workspace.
-- TODO: Gets slow when there are many windows. Optimize! Not a problem
--       for casual use however.
--
------------------------------------------------------------------------------

module Pager (
    labeledPager
  ) where

-- XMonad modules
import XMonad
import Data.Char (toLower)
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

-- Custom modules
import App
import Config
import MyApps
import Utils

-- | The 'DynamicLog' logger to add to 'ppExtras' using the given pretty
--   printer and window label map.
labeledPager :: PP -> X (Maybe String)
labeledPager pp = do
    s       <- gets windowset
    urgents <- readUrgents
    sort'   <- ppSort pp
    wl      <- queryWindows s windowLabelMap
    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 ws) pp $ print path ws
      where
        path
            | W.tag ws == this               = hilightIconPath
            | W.tag ws == summonWorkspaceTag = grayIconPath
            | W.tag ws == hiddenWorkspaceTag = grayIconPath
            | otherwise                      = iconPath

    printer ws
        | 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

    print path ws = printWindows path wl (W.integrate' $ W.stack ws)

-- | 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 :: String              -- ^ icon path
             -> M.Map Window String -- ^ window to symbol map
             -> [Window]            -- ^ windows on the workspace
             -> String
printWindows path wl ws = handleEmpty $ intercalate (icon path "sep.xpm"$ map (\-> icon path $ fromMaybe defaultIcon (M.lookup w wl)) ws
  where

    icon path i = "^i(" ++ path ++ i ++ ")"

    handleEmpty "" = "^ro(6x6)"
    handleEmpty 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, defaultIcon)
    qw ((l, q):lqs) w = runQuery q w >>= if_ (return (w, l)) (qw lqs w)


-- | Map windows to symbols for the pager.  Symbols for floating windows are in
--   lower case.
windowLabelMap :: [(String, Query Bool)]
windowLabelMap =
    map whenFloat windows ++ windows
  where

    whenFloat (l, q) = (map toLower l, isFloat <&&> q)

    windows = zip (map icon apps) (map query apps)