{-# OPTIONS_GHC -fno-warn-missing-signatures #-}
{-# LANGUAGE MultiParamTypeClasses, Rank2Types, ViewPatterns #-}

-----------------------------------------------------------------------------
-- |
-- Module      :  XMonad.Layout.Groups.Helpers
-- Description :  Utility functions for "XMonad.Layout.Groups".
-- 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 (sendMessageB)

import XMonad.Prelude (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.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 layoutHook and key bindings, see
-- <https://xmonad.org/TUTORIAL.html the tutorial> and "XMonad.Doc.Extending".

-- ** Layout-generic actions
-- #Layout-generic actions#

alt :: G.ModifySpec -> (WindowSet -> WindowSet) -> X ()
alt :: ModifySpec -> (WindowSet -> WindowSet) -> X ()
alt ModifySpec
f WindowSet -> WindowSet
g = GroupsMessage -> X () -> X ()
alt2 (ModifySpec -> GroupsMessage
G.Modify WithID l Window
-> Zipper (Group l Window) -> Zipper (Group l Window)
ModifySpec
f) (X () -> X ()) -> X () -> X ()
forall a b. (a -> b) -> a -> b
$ (WindowSet -> WindowSet) -> X ()
windows WindowSet -> WindowSet
g

alt2 :: G.GroupsMessage -> X () -> X ()
alt2 :: GroupsMessage -> X () -> X ()
alt2 GroupsMessage
m X ()
x = do Bool
b <- GroupsMessage -> X Bool
forall a. Message a => a -> X Bool
sendMessageB GroupsMessage
m
              Bool -> X () -> X ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless Bool
b X ()
x

-- | Swap the focused window with the previous one
swapUp :: X ()
swapUp :: X ()
swapUp = ModifySpec -> (WindowSet -> WindowSet) -> X ()
alt WithID l Window
-> Zipper (Group l Window) -> Zipper (Group l Window)
ModifySpec
G.swapUp WindowSet -> WindowSet
forall i l a s sd. StackSet i l a s sd -> StackSet i l a s sd
W.swapUp

-- | Swap the focused window with the next one
swapDown :: X ()
swapDown :: X ()
swapDown = ModifySpec -> (WindowSet -> WindowSet) -> X ()
alt WithID l Window
-> Zipper (Group l Window) -> Zipper (Group l Window)
ModifySpec
G.swapDown WindowSet -> WindowSet
forall i l a s sd. StackSet i l a s sd -> StackSet i l a s sd
W.swapDown

-- | Swap the focused window with the master window
swapMaster :: X ()
swapMaster :: X ()
swapMaster = ModifySpec -> (WindowSet -> WindowSet) -> X ()
alt WithID l Window
-> Zipper (Group l Window) -> Zipper (Group l Window)
ModifySpec
G.swapMaster WindowSet -> WindowSet
forall i l a s sd. StackSet i l a s sd -> StackSet i l a s sd
W.swapMaster

-- | If the focused window is floating, focus the next floating
-- window. otherwise, focus the next non-floating one.
focusUp :: X ()
focusUp :: X ()
focusUp = X () -> X () -> X ()
ifFloat X ()
focusFloatUp X ()
focusNonFloatUp

-- | If the focused window is floating, focus the next floating
-- window. otherwise, focus the next non-floating one.
focusDown :: X ()
focusDown :: X ()
focusDown = X () -> X () -> X ()
ifFloat X ()
focusFloatDown X ()
focusNonFloatDown

-- | Move focus to the master window
focusMaster :: X ()
focusMaster :: X ()
focusMaster = ModifySpec -> (WindowSet -> WindowSet) -> X ()
alt WithID l Window
-> Zipper (Group l Window) -> Zipper (Group l Window)
ModifySpec
G.focusMaster WindowSet -> WindowSet
forall i l a s sd. StackSet i l a s sd -> StackSet i l a s sd
W.shiftMaster

-- | Move focus between the floating and non-floating layers
toggleFocusFloat :: X ()
toggleFocusFloat :: X ()
toggleFocusFloat = X () -> X () -> X ()
ifFloat X ()
focusNonFloat X ()
focusFloatUp

-- *** Floating layer helpers

getFloats :: X [Window]
getFloats :: X [Window]
getFloats = (XState -> [Window]) -> X [Window]
forall s (m :: * -> *) a. MonadState s m => (s -> a) -> m a
gets ((XState -> [Window]) -> X [Window])
-> (XState -> [Window]) -> X [Window]
forall a b. (a -> b) -> a -> b
$ Map Window RationalRect -> [Window]
forall k a. Map k a -> [k]
M.keys (Map Window RationalRect -> [Window])
-> (XState -> Map Window RationalRect) -> XState -> [Window]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. WindowSet -> Map Window RationalRect
forall i l a sid sd. StackSet i l a sid sd -> Map a RationalRect
W.floating (WindowSet -> Map Window RationalRect)
-> (XState -> WindowSet) -> XState -> Map Window RationalRect
forall b c a. (b -> c) -> (a -> b) -> a -> c
. XState -> WindowSet
windowset

getWindows :: X [Window]
getWindows :: X [Window]
getWindows = (XState -> [Window]) -> X [Window]
forall s (m :: * -> *) a. MonadState s m => (s -> a) -> m a
gets ((XState -> [Window]) -> X [Window])
-> (XState -> [Window]) -> X [Window]
forall a b. (a -> b) -> a -> b
$ Maybe (Stack Window) -> [Window]
forall a. Maybe (Stack a) -> [a]
W.integrate' (Maybe (Stack Window) -> [Window])
-> (XState -> Maybe (Stack Window)) -> XState -> [Window]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Workspace WorkspaceId (Layout Window) Window
-> Maybe (Stack Window)
forall i l a. Workspace i l a -> Maybe (Stack a)
W.stack (Workspace WorkspaceId (Layout Window) Window
 -> Maybe (Stack Window))
-> (XState -> Workspace WorkspaceId (Layout Window) Window)
-> XState
-> Maybe (Stack Window)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Screen WorkspaceId (Layout Window) Window ScreenId ScreenDetail
-> Workspace WorkspaceId (Layout Window) Window
forall i l a sid sd. Screen i l a sid sd -> Workspace i l a
W.workspace (Screen WorkspaceId (Layout Window) Window ScreenId ScreenDetail
 -> Workspace WorkspaceId (Layout Window) Window)
-> (XState
    -> Screen WorkspaceId (Layout Window) Window ScreenId ScreenDetail)
-> XState
-> Workspace WorkspaceId (Layout Window) Window
forall b c a. (b -> c) -> (a -> b) -> a -> c
. WindowSet
-> Screen WorkspaceId (Layout Window) Window ScreenId ScreenDetail
forall i l a sid sd. StackSet i l a sid sd -> Screen i l a sid sd
W.current (WindowSet
 -> Screen WorkspaceId (Layout Window) Window ScreenId ScreenDetail)
-> (XState -> WindowSet)
-> XState
-> Screen WorkspaceId (Layout Window) Window ScreenId ScreenDetail
forall b c a. (b -> c) -> (a -> b) -> a -> c
. XState -> WindowSet
windowset

ifFloat :: X () -> X () -> X ()
ifFloat :: X () -> X () -> X ()
ifFloat X ()
x1 X ()
x2 = (Window -> X ()) -> X ()
withFocused ((Window -> X ()) -> X ()) -> (Window -> X ()) -> X ()
forall a b. (a -> b) -> a -> b
$ \Window
w -> do [Window]
floats <- X [Window]
getFloats
                                       if Window
w Window -> [Window] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Window]
floats then X ()
x1 else X ()
x2

focusNonFloat :: X ()
focusNonFloat :: X ()
focusNonFloat = GroupsMessage -> X () -> X ()
alt2 GroupsMessage
G.Refocus X ()
helper
    where helper :: X ()
helper = (Window -> X ()) -> X ()
withFocused ((Window -> X ()) -> X ()) -> (Window -> X ()) -> X ()
forall a b. (a -> b) -> a -> b
$ \Window
w -> do
                     [Window]
ws <- X [Window]
getWindows
                     [Window]
floats <- X [Window]
getFloats
                     let ([Window]
before,  [Window]
after) = (Window -> Bool) -> [Window] -> ([Window], [Window])
forall a. (a -> Bool) -> [a] -> ([a], [a])
span (Window -> Window -> Bool
forall a. Eq a => a -> a -> Bool
/=Window
w) [Window]
ws
                     case (Window -> Bool) -> [Window] -> [Window]
forall a. (a -> Bool) -> [a] -> [a]
filter (Window -> [Window] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` [Window]
floats) ([Window] -> [Window]) -> [Window] -> [Window]
forall a b. (a -> b) -> a -> b
$ [Window]
after [Window] -> [Window] -> [Window]
forall a. [a] -> [a] -> [a]
++ [Window]
before of
                       [] -> () -> X ()
forall a. a -> X a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
                       Window
w':[Window]
_ -> Window -> X ()
focus Window
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 :: (Bool -> Bool) -> ([Window] -> [Window]) -> X ()
focusHelper Bool -> Bool
f [Window] -> [Window]
g = (Window -> X ()) -> X ()
withFocused ((Window -> X ()) -> X ()) -> (Window -> X ()) -> X ()
forall a b. (a -> b) -> a -> b
$ \Window
w -> do
                 [Window]
ws <- X [Window]
getWindows
                 let ([Window]
before, Int -> [Window] -> [Window]
forall a. Int -> [a] -> [a]
drop Int
1 -> [Window]
after) = (Window -> Bool) -> [Window] -> ([Window], [Window])
forall a. (a -> Bool) -> [a] -> ([a], [a])
span (Window -> Window -> Bool
forall a. Eq a => a -> a -> Bool
/=Window
w) [Window]
ws
                 let toFocus :: [Window]
toFocus = [Window] -> [Window]
g ([Window] -> [Window]) -> [Window] -> [Window]
forall a b. (a -> b) -> a -> b
$ [Window]
after [Window] -> [Window] -> [Window]
forall a. [a] -> [a] -> [a]
++ [Window]
before
                 [Window]
floats <- X [Window]
getFloats
                 case (Window -> Bool) -> [Window] -> [Window]
forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
f (Bool -> Bool) -> (Window -> Bool) -> Window -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Window -> [Window] -> Bool) -> [Window] -> Window -> Bool
forall a b c. (a -> b -> c) -> b -> a -> c
flip Window -> [Window] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
elem [Window]
floats) [Window]
toFocus of
                   [] -> () -> X ()
forall a. a -> X a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
                   Window
w':[Window]
_ -> Window -> X ()
focus Window
w'


focusNonFloatUp :: X ()
focusNonFloatUp :: X ()
focusNonFloatUp = GroupsMessage -> X () -> X ()
alt2 (ModifySpec -> GroupsMessage
G.Modify WithID l Window
-> Zipper (Group l Window) -> Zipper (Group l Window)
ModifySpec
G.focusUp) (X () -> X ()) -> X () -> X ()
forall a b. (a -> b) -> a -> b
$ (Bool -> Bool) -> ([Window] -> [Window]) -> X ()
focusHelper Bool -> Bool
not [Window] -> [Window]
forall a. [a] -> [a]
reverse

focusNonFloatDown :: X ()
focusNonFloatDown :: X ()
focusNonFloatDown = GroupsMessage -> X () -> X ()
alt2 (ModifySpec -> GroupsMessage
G.Modify WithID l Window
-> Zipper (Group l Window) -> Zipper (Group l Window)
ModifySpec
G.focusDown) (X () -> X ()) -> X () -> X ()
forall a b. (a -> b) -> a -> b
$ (Bool -> Bool) -> ([Window] -> [Window]) -> X ()
focusHelper Bool -> Bool
not [Window] -> [Window]
forall a. a -> a
id

focusFloatUp :: X ()
focusFloatUp :: X ()
focusFloatUp = (Bool -> Bool) -> ([Window] -> [Window]) -> X ()
focusHelper Bool -> Bool
forall a. a -> a
id [Window] -> [Window]
forall a. [a] -> [a]
reverse

focusFloatDown :: X ()
focusFloatDown :: X ()
focusFloatDown = (Bool -> Bool) -> ([Window] -> [Window]) -> X ()
focusHelper Bool -> Bool
forall a. a -> a
id [Window] -> [Window]
forall a. a -> a
id


-- ** Groups-specific actions

wrap :: G.ModifySpec -> X ()
wrap :: ModifySpec -> X ()
wrap ModifySpec
x = GroupsMessage -> X ()
forall a. Message a => a -> X ()
sendMessage (ModifySpec -> GroupsMessage
G.Modify WithID l Window
-> Zipper (Group l Window) -> Zipper (Group l Window)
ModifySpec
x)

-- | Swap the focused group with the previous one
swapGroupUp :: X ()
swapGroupUp :: X ()
swapGroupUp = ModifySpec -> X ()
wrap WithID l Window
-> Zipper (Group l Window) -> Zipper (Group l Window)
ModifySpec
G.swapGroupUp

-- | Swap the focused group with the next one
swapGroupDown :: X ()
swapGroupDown :: X ()
swapGroupDown = ModifySpec -> X ()
wrap WithID l Window
-> Zipper (Group l Window) -> Zipper (Group l Window)
ModifySpec
G.swapGroupDown

-- | Swap the focused group with the master group
swapGroupMaster :: X ()
swapGroupMaster :: X ()
swapGroupMaster = ModifySpec -> X ()
wrap WithID l Window
-> Zipper (Group l Window) -> Zipper (Group l Window)
ModifySpec
G.swapGroupMaster

-- | Move the focus to the previous group
focusGroupUp :: X ()
focusGroupUp :: X ()
focusGroupUp = ModifySpec -> X ()
wrap WithID l Window
-> Zipper (Group l Window) -> Zipper (Group l Window)
ModifySpec
G.focusGroupUp

-- | Move the focus to the next group
focusGroupDown :: X ()
focusGroupDown :: X ()
focusGroupDown = ModifySpec -> X ()
wrap WithID l Window
-> Zipper (Group l Window) -> Zipper (Group l Window)
ModifySpec
G.focusGroupDown

-- | Move the focus to the master group
focusGroupMaster :: X ()
focusGroupMaster :: X ()
focusGroupMaster = ModifySpec -> X ()
wrap WithID l Window
-> Zipper (Group l Window) -> Zipper (Group l Window)
ModifySpec
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 :: Bool -> X ()
moveToGroupUp Bool
b = ModifySpec -> X ()
wrap (Bool -> ModifySpec
G.moveToGroupUp Bool
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 :: Bool -> X ()
moveToGroupDown Bool
b = ModifySpec -> X ()
wrap (Bool -> ModifySpec
G.moveToGroupDown Bool
b)

-- | Move the focused window to a new group before the current one
moveToNewGroupUp :: X ()
moveToNewGroupUp :: X ()
moveToNewGroupUp = ModifySpec -> X ()
wrap WithID l Window
-> Zipper (Group l Window) -> Zipper (Group l Window)
ModifySpec
G.moveToNewGroupUp

-- | Move the focused window to a new group after the current one
moveToNewGroupDown :: X ()
moveToNewGroupDown :: X ()
moveToNewGroupDown = ModifySpec -> X ()
wrap WithID l Window
-> Zipper (Group l Window) -> Zipper (Group l Window)
ModifySpec
G.moveToNewGroupDown

-- | Split the focused group in two at the position of the focused
-- window.
splitGroup :: X ()
splitGroup :: X ()
splitGroup = ModifySpec -> X ()
wrap WithID l Window
-> Zipper (Group l Window) -> Zipper (Group l Window)
ModifySpec
G.splitGroup