content: {-# OPTIONS_GHC -fno-warn-missing-signatures #-}
  {-# LANGUAGE MultiParamTypeClasses, Rank2Types, ImpredicativeTypes #-}
  
  -----------------------------------------------------------------------------
  -- |
  -- Module      :  XMonad.Layout.Groups.Helpers
  -- Copyright   :  Quentin Moser <moserq@gmail.com>
  -- License     :  BSD-style (see LICENSE)
  --
  -- Maintainer  :  orphaned
  -- Stability   :  stable
  -- Portability :  unportable
  --
  -- Utility functions for "XMonad.Layout.Groups".
  --
  -----------------------------------------------------------------------------
  
  module XMonad.Layout.Groups.Helpers ( -- * Usage
                                        -- $usage
  
                                        -- ** Layout-generic actions
                                        swapUp
                                      , swapDown
                                      , swapMaster
                                      , focusUp
                                      , focusDown
                                      , focusMaster
                                      , toggleFocusFloat
  
                                        -- ** 'G.Groups'-secific actions
                                      , swapGroupUp
                                      , swapGroupDown
                                      , swapGroupMaster
                                      , focusGroupUp
                                      , focusGroupDown
                                      , focusGroupMaster
                                      , moveToGroupUp
                                      , moveToGroupDown
                                      , moveToNewGroupUp
                                      , moveToNewGroupDown
                                      , splitGroup ) where
  
  import XMonad hiding ((|||))
  import qualified XMonad.StackSet as W
  
  import qualified XMonad.Layout.Groups as G
  
  import XMonad.Actions.MessageFeedback
  
  import Control.Monad (unless)
  import qualified Data.Map as M
  
  -- $usage
  --
  -- This module provides helpers functions for use with "XMonad.Layout.Groups"-based
  -- layouts. You can use its contents by adding
  --
  -- > import XMonad.Layout.Groups.Helpers
  --
  -- to the top of your @.\/.xmonad\/xmonad.hs@.
  --
  -- "XMonad.Layout.Groups"-based layouts do not have the same notion
  -- of window ordering as the rest of XMonad. For this reason, the usual
  -- ways of reordering windows and moving focus do not work with them.
  -- "XMonad.Layout.Groups" provides 'Message's that can be used to obtain
  -- the right effect.
  --
  -- But what if you want to use both 'G.Groups' and other layouts?
  -- This module provides actions that try to send 'G.GroupsMessage's, and
  -- fall back to the classic way if the current layout doesn't hande them.
  -- They are in the section called \"Layout-generic actions\".
  --
  -- The sections \"Groups-specific actions\" contains actions that don't make
  -- sense for non-'G.Groups'-based layouts. These are simply wrappers around
  -- the equivalent 'G.GroupsMessage's, but are included so you don't have to
  -- write @sendMessage $ Modify $ ...@ everytime.
  --
  -- This module exports many operations with the same names as
  -- 'G.ModifySpec's from "XMonad.Layout.Groups", so if you want
  -- to import both, we suggest to import "XMonad.Layout.Groups"
  -- qualified:
  --
  -- > import qualified XMonad.Layout.Groups as G
  --
  -- For more information on how to extend your layour hook and key bindings, see
  -- "XMonad.Doc.Extending".
  
  -- ** Layout-generic actions
  -- #Layout-generic actions#
  
  alt :: G.ModifySpec -> (WindowSet -> WindowSet) -> X ()
  alt f g = alt2 (G.Modify f) $ windows g
  
  alt2 :: G.GroupsMessage -> X () -> X ()
  alt2 m x = do b <- send m
                unless b x
  
  -- | Swap the focused window with the previous one
  swapUp :: X ()
  swapUp = alt G.swapUp W.swapUp
  
  -- | Swap the focused window with the next one
  swapDown :: X ()
  swapDown = alt G.swapDown W.swapDown
  
  -- | Swap the focused window with the master window
  swapMaster :: X ()
  swapMaster = alt G.swapMaster W.swapMaster
  
  -- | If the focused window is floating, focus the next floating
  -- window. otherwise, focus the next non-floating one.
  focusUp :: X ()
  focusUp = ifFloat focusFloatUp focusNonFloatUp
  
  -- | If the focused window is floating, focus the next floating
  -- window. otherwise, focus the next non-floating one.
  focusDown :: X ()
  focusDown = ifFloat focusFloatDown focusNonFloatDown
  
  -- | Move focus to the master window
  focusMaster :: X ()
  focusMaster = alt G.focusMaster W.shiftMaster
  
  -- | Move focus between the floating and non-floating layers
  toggleFocusFloat :: X ()
  toggleFocusFloat = ifFloat focusNonFloat focusFloatUp
  
  -- *** Floating layer helpers
  
  getFloats :: X [Window]
  getFloats = gets $ M.keys . W.floating . windowset
  
  getWindows :: X [Window]
  getWindows = gets $ W.integrate' . W.stack . W.workspace . W.current . windowset
  
  ifFloat :: X () -> X () -> X ()
  ifFloat x1 x2 = withFocused $ \w -> do floats <- getFloats
                                         if elem w floats then x1 else x2
  
  focusNonFloat :: X ()
  focusNonFloat = alt2 G.Refocus helper
      where helper = withFocused $ \w -> do
                       ws <- getWindows
                       floats <- getFloats
                       let (before,  after) = span (/=w) ws
                       case filter (flip notElem floats) $ after ++ before of
                         [] -> return ()
                         w':_ -> focus w'
  
  focusHelper :: (Bool -> Bool) -- ^ if you want to focus a floating window, 'id'.
                                -- if you want a non-floating one, 'not'.
              -> ([Window] -> [Window]) -- ^ if you want the next window, 'id'.
                                        -- if you want the previous one, 'reverse'.
              -> X ()
  focusHelper f g = withFocused $ \w -> do
                   ws <- getWindows
                   let (before, _:after) = span (/=w) ws
                   let toFocus = g $ after ++ before
                   floats <- getFloats
                   case filter (f . flip elem floats) toFocus of
                     [] -> return ()
                     w':_ -> focus w'
  
  
  focusNonFloatUp :: X ()
  focusNonFloatUp = alt2 (G.Modify G.focusUp) $ focusHelper not reverse
  
  focusNonFloatDown :: X ()
  focusNonFloatDown = alt2 (G.Modify G.focusDown) $ focusHelper not id
  
  focusFloatUp :: X ()
  focusFloatUp = focusHelper id reverse
  
  focusFloatDown :: X ()
  focusFloatDown = focusHelper id id
  
  
  -- ** Groups-specific actions
  
  wrap :: G.ModifySpec -> X ()
  wrap = sendMessage . G.Modify
  
  -- | Swap the focused group with the previous one
  swapGroupUp :: X ()
  swapGroupUp = wrap G.swapGroupUp
  
  -- | Swap the focused group with the next one
  swapGroupDown :: X ()
  swapGroupDown = wrap G.swapGroupDown
  
  -- | Swap the focused group with the master group
  swapGroupMaster :: X ()
  swapGroupMaster = wrap G.swapGroupMaster
  
  -- | Move the focus to the previous group
  focusGroupUp :: X ()
  focusGroupUp = wrap G.focusGroupUp
  
  -- | Move the focus to the next group
  focusGroupDown :: X ()
  focusGroupDown = wrap G.focusGroupDown
  
  -- | Move the focus to the master group
  focusGroupMaster :: X ()
  focusGroupMaster = wrap G.focusGroupMaster
  
  -- | Move the focused window to the previous group. The 'Bool' argument
  -- determines what will be done if the focused window is in the very first
  -- group: Wrap back to the end ('True'), or create a new group before
  -- it ('False').
  moveToGroupUp :: Bool -> X ()
  moveToGroupUp b = wrap (G.moveToGroupUp b)
  
  -- | Move the focused window to the next group. The 'Bool' argument
  -- determines what will be done if the focused window is in the very last
  -- group: Wrap back to the beginning ('True'), or create a new group after
  -- it ('False').
  moveToGroupDown :: Bool -> X ()
  moveToGroupDown b = wrap (G.moveToGroupDown b)
  
  -- | Move the focused window to a new group before the current one
  moveToNewGroupUp :: X ()
  moveToNewGroupUp = wrap G.moveToNewGroupUp
  
  -- | Move the focused window to a new group after the current one
  moveToNewGroupDown :: X ()
  moveToNewGroupDown = wrap G.moveToNewGroupDown
  
  -- | Split the focused group in two at the position of the focused
  -- window.
  splitGroup :: X ()
  splitGroup = wrap G.splitGroup
  