    -----------------------------------------------------------------------------
    -- |
    -- Module      :  XMonad.Actions.WindowBringer
    -- Copyright   :  Devin Mullins <me@twifkak.com>
    -- License     :  BSD-style (see LICENSE)
    --
    -- Maintainer  :  Devin Mullins <me@twifkak.com>
    -- Stability   :  stable
    -- Portability :  unportable
    --
    -- dmenu operations to bring windows to you, and bring you to windows.
    -- That is to say, it pops up a dmenu with window names, in case you forgot
    -- where you left your XChat.
    --
    -----------------------------------------------------------------------------

    module XMonad.Actions.WindowBringer (
                        -- * Usage
                        -- $usage
                        gotoMenu, gotoMenu', gotoMenuArgs, gotoMenuArgs',
                        bringMenu, bringMenu', bringMenuArgs, bringMenuArgs',
                        windowMap,
                        bringWindow
                       ) where

    import Data.Char (toLower)
    import qualified Data.Map as M

    import qualified XMonad.StackSet as W
    import XMonad
    import qualified XMonad as X
    import XMonad.Util.Dmenu (menuMapArgs)
    import XMonad.Util.NamedWindows (getName)

    -- $usage
    --
    -- Import the module into your @~\/.xmonad\/xmonad.hs@:
    --
    -- > import XMonad.Actions.WindowBringer
    --
    -- and define appropriate key bindings:
    --
    -- > , ((modm .|. shiftMask, xK_g     ), gotoMenu)
    -- > , ((modm .|. shiftMask, xK_b     ), bringMenu)
    --
    -- For detailed instructions on editing your key bindings, see
    -- "XMonad.Doc.Extending#Editing_key_bindings".

    -- | Default menu command
    defaultCmd :: String
    defaultCmd = "dmenu"

    -- | Pops open a dmenu with window titles. Choose one, and you will be
    --   taken to the corresponding workspace.
    gotoMenu :: X ()
    gotoMenu = gotoMenuArgs []

    -- | Pops open a dmenu with window titles. Choose one, and you will be
    --   taken to the corresponding workspace. This version takes a list of
    --   arguments to pass to dmenu.
    gotoMenuArgs :: [String] -> X ()
    gotoMenuArgs menuArgs = gotoMenuArgs' defaultCmd menuArgs

    -- | Pops open an application with window titles given over stdin. Choose one,
    --   and you will be taken to the corresponding workspace.
    gotoMenu' :: String -> X ()
    gotoMenu' menuCmd = gotoMenuArgs' menuCmd []

    -- | Pops open an application with window titles given over stdin. Choose one,
    --   and you will be taken to the corresponding workspace. This version takes a
    --   list of arguments to pass to dmenu.
    gotoMenuArgs' :: String -> [String] -> X ()
    gotoMenuArgs' menuCmd menuArgs = actionMenu menuCmd menuArgs W.focusWindow

    -- | Pops open a dmenu with window titles. Choose one, and it will be
    --   dragged, kicking and screaming, into your current workspace.
    bringMenu :: X ()
    bringMenu = bringMenuArgs []

    -- | Pops open a dmenu with window titles. Choose one, and it will be
    --   dragged, kicking and screaming, into your current workspace. This version
    --   takes a list of arguments to pass to dmenu.
    bringMenuArgs :: [String] -> X ()
    bringMenuArgs menuArgs = bringMenuArgs' defaultCmd menuArgs

    -- | Pops open an application with window titles given over stdin. Choose one,
    --   and it will be dragged, kicking and screaming, into your current
    --   workspace.
    bringMenu' :: String -> X ()
    bringMenu' menuCmd = bringMenuArgs' menuCmd []

    -- | Pops open an application with window titles given over stdin. Choose one,
    --   and it will be dragged, kicking and screaming, into your current
    --   workspace. This version allows arguments to the chooser to be specified.
    bringMenuArgs' :: String -> [String] -> X ()
    bringMenuArgs' menuCmd menuArgs = actionMenu menuCmd menuArgs bringWindow

    -- | Brings the specified window into the current workspace.
    bringWindow :: Window -> X.WindowSet -> X.WindowSet
    bringWindow w ws = W.shiftWin (W.currentTag ws) w ws

    -- | Calls dmenuMap to grab the appropriate Window, and hands it off to action
    --   if found.
    actionMenu :: String -> [String] -> (Window -> X.WindowSet -> X.WindowSet) -> X ()
    actionMenu menuCmd menuArgs action = windowMap >>= menuMapFunction >>= flip X.whenJust (windows . action)
        where
          menuMapFunction :: M.Map String a -> X (Maybe a)
          menuMapFunction selectionMap = menuMapArgs menuCmd menuArgs selectionMap

    -- | A map from window names to Windows.
    windowMap :: X (M.Map String Window)
    windowMap = do
      ws <- gets X.windowset
      M.fromList `fmap` concat `fmap` mapM keyValuePairs (W.workspaces ws)
     where keyValuePairs ws = mapM (keyValuePair ws) $ W.integrate' (W.stack ws)
           keyValuePair ws w = flip (,) w `fmap` decorateName ws w

    -- | Returns the window name as will be listed in dmenu.
    --   Lowercased, for your convenience (since dmenu is case-sensitive).
    --   Tagged with the workspace ID, to guarantee uniqueness, and to let the user
    --   know where he's going.
    decorateName :: X.WindowSpace -> Window -> X String
    decorateName ws w = do
      name <- fmap (map toLower . show) $ getName w
      return $ name ++ " [" ++ W.tag ws ++ "]"