# LANGUAGE MultiParamTypeClasses , DeriveDataTypeable , TypeSynonymInstances , FlexibleContexts , PatternGuards #
----------------------------------------------------------------------------
-- |
-- Module      :  XMonad.Layout.Minimize
  Copyright    :   ( c ) Jan Vornberger 2009 ,   2010
-- License     :  BSD3-style (see LICENSE)
--
-- Maintainer  :  
-- Stability   :  unstable
-- Portability :  not portable
--
-- Makes it possible to minimize windows, temporarily removing them
-- from the layout until they are restored.
--
-----------------------------------------------------------------------------

module XMonad.Layout.Minimize (
        -- * Usage
        -- $usage
        minimize,
        minimizeWindow,
        MinimizeMsg(RestoreMinimizedWin,RestoreNextMinimizedWin,MinimizeWin),
        Minimize,
        getMinimizedWindows,
        getCurrentMinimizedWindows,
        setMinimized,
        setNotMinimized
    ) where

import XMonad
import qualified XMonad.StackSet as W
import XMonad.Layout.LayoutModifier
import XMonad.Layout.BoringWindows as BW
import XMonad.Util.WindowProperties (getProp32)
import Data.List
import qualified Data.Map as M
import qualified XMonad.Util.ExtensibleState as XS
import Data.Maybe
import Foreign.C.Types (CLong)

-- $usage
-- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@:
--
  > import XMonad . Layout . Minimize
--
-- Then edit your @layoutHook@ by adding the Minimize layout modifier:
--
  > myLayout = minimize ( Tall 1 ( 3/100 ) ( 1/2 ) ) ||| Full ||| etc ..
-- > main = xmonad def { layoutHook = myLayout }
--
-- For more detailed instructions on editing the layoutHook see:
--
-- "XMonad.Doc.Extending#Editing_the_layout_hook"
--
-- In the key-bindings, do something like:
--
  >         , ( ( modm ,                xK_m      ) ,   )
-- >        , ((modm .|. shiftMask, xK_m     ), sendMessage RestoreNextMinimizedWin)
--
  The first action will minimize the focused window , while the second one will restore
-- the next minimized window.
--
-- For detailed instruction on editing the key binding see:
--
  " XMonad . Doc . Extending#Editing_key_bindings " .
--
  The module is designed to work together with " XMonad . Layout . BoringWindows " so
-- that minimized windows will be skipped over when switching the focused window with
-- the keyboard. Include 'BW.boringWindows' in your layout hook and see the
  documentation of " XMonad . Layout . BoringWindows " on how to modify your keybindings .
--
-- Also see "XMonad.Hooks.Minimize" if you want to be able to minimize
-- and restore windows from your taskbar.

data Minimize a = Minimize [Window] (M.Map Window W.RationalRect) deriving ( Read, Show )
minimize :: LayoutClass l Window => l Window -> ModifiedLayout Minimize l Window
minimize = ModifiedLayout $ Minimize [] M.empty

data MinimizeMsg = MinimizeWin Window
                    | RestoreMinimizedWin Window
                    | RestoreNextMinimizedWin
                    | MinimizedSave
                    deriving (Typeable, Eq)
instance Message MinimizeMsg

minimizeWindow :: Window -> X ()
minimizeWindow w = sendMessage (MinimizeWin w) >> BW.focusDown

setMinimizedState :: Window -> Int -> (CLong -> [CLong] -> [CLong]) -> X ()
setMinimizedState win st f = do
    setWMState win st
    withDisplay $ \dpy -> do
        wm_state <- getAtom "_NET_WM_STATE"
        mini <- getAtom "_NET_WM_STATE_HIDDEN"
        wstate <- fromMaybe [] `fmap` getProp32 wm_state win
        let ptype = 4 -- The atom property type for changeProperty
            fi_mini = fromIntegral mini
        io $ changeProperty32 dpy win wm_state ptype propModeReplace (f fi_mini wstate)

setMinimized :: Window -> X ()
setMinimized win = setMinimizedState win iconicState (:)

setNotMinimized :: Window -> X ()
setNotMinimized win = setMinimizedState win normalState delete

data MinimizedWindows = MinimizedWindows [Window] deriving (Read, Show, Typeable)
instance ExtensionClass MinimizedWindows where
    initialValue = MinimizedWindows []
    extensionType = PersistentExtension

getMinimizedWindows l = do
    res <- handleMessage l (SomeMessage MinimizedSave) `catchX` return Nothing
    case res of
         Just _ -> do
             MinimizedWindows wins <- XS.get
             return wins
         _ -> return []

getCurrentMinimizedWindows = gets (W.layout . W.workspace . W.current . windowset) >>= getMinimizedWindows

-- filter the list such that those non-existent ones are avoided
correctWins ls ws = filter (`elem` (W.allWindows ws)) ls

instance LayoutModifier Minimize Window where
    modifierDescription _ = "Minimize"

    modifyLayout (Minimize minimized _) wksp rect = do
        let stack = W.stack wksp
            filtStack = stack >>=W.filter (\w -> not (w `elem` minimized))
        runLayout (wksp {W.stack = filtStack}) rect

    handleMess ol@(Minimize minimized unfloated) m
        | Just (MinimizeWin w) <- fromMessage m, not (w `elem` minimized) = do
                setMinimized w
                ws <- gets windowset
                case M.lookup w (W.floating ws) of
                  Nothing -> return $ Just $ Minimize (w:minimized) unfloated
                  Just r -> do
                    modify (\s -> s { windowset = W.sink w ws})
                    return $ Just $ Minimize (w:minimized) (M.insert w r unfloated)
        | Just (RestoreMinimizedWin w) <- fromMessage m = do
            setNotMinimized w
            case M.lookup w unfloated of
              Nothing -> return $ Just $ Minimize (minimized \\ [w]) unfloated
              Just r -> do
                ws <- gets windowset
                modify (\s -> s { windowset = W.float w r ws})
                return $ Just $ Minimize (minimized \\ [w]) (M.delete w unfloated)
        | Just RestoreNextMinimizedWin <- fromMessage m = do
          ws <- gets windowset
          let cminimized = correctWins minimized ws
          -- should check if the window is valid (still exists)
          if not (null cminimized)
            then case M.lookup (head cminimized) unfloated of
              Nothing -> do
                let w = head cminimized
                setNotMinimized w
                modify (\s -> s { windowset = W.focusWindow w ws})
                return $ Just $ Minimize (tail cminimized) unfloated
              Just r -> do
                let w = head cminimized
                setNotMinimized w
                modify (\s -> s { windowset = (W.focusWindow w . W.float w r) ws})
                return $ Just $ Minimize (tail cminimized) (M.delete w unfloated)
            else return Nothing
        | Just MinimizedSave <- fromMessage m = do
            -- just save the minimized list to the data
            ws <- gets windowset
            XS.put $ MinimizedWindows (correctWins minimized ws)
            return $ Just ol
        | Just BW.UpdateBoring <- fromMessage m = do
            ws <- gets (W.workspace . W.current . windowset)
            flip sendMessageWithNoRefresh ws $ BW.Replace "Minimize" minimized
            return Nothing
        | otherwise = return Nothing

--------------------------------------------------------------------------
 |
 Module      :  XMonad.Layout.Minimize
 License     :  BSD3-style (see LICENSE)

 Maintainer  :  
 Stability   :  unstable
 Portability :  not portable

 Makes it possible to minimize windows, temporarily removing them
 from the layout until they are restored.

---------------------------------------------------------------------------
 * Usage
 $usage
 $usage
 You can use this module with the following in your @~\/.xmonad\/xmonad.hs@:


 Then edit your @layoutHook@ by adding the Minimize layout modifier:

 > main = xmonad def { layoutHook = myLayout }

 For more detailed instructions on editing the layoutHook see:

 "XMonad.Doc.Extending#Editing_the_layout_hook"

 In the key-bindings, do something like:

 >        , ((modm .|. shiftMask, xK_m     ), sendMessage RestoreNextMinimizedWin)

 the next minimized window.

 For detailed instruction on editing the key binding see:


 that minimized windows will be skipped over when switching the focused window with
 the keyboard. Include 'BW.boringWindows' in your layout hook and see the

 Also see "XMonad.Hooks.Minimize" if you want to be able to minimize
 and restore windows from your taskbar.
 The atom property type for changeProperty
 filter the list such that those non-existent ones are avoided
 should check if the window is valid (still exists)
 just save the minimized list to the data
# LANGUAGE MultiParamTypeClasses , DeriveDataTypeable , TypeSynonymInstances , FlexibleContexts , PatternGuards #
  Copyright    :   ( c ) Jan Vornberger 2009 ,   2010

module XMonad.Layout.Minimize (
        minimize,
        minimizeWindow,
        MinimizeMsg(RestoreMinimizedWin,RestoreNextMinimizedWin,MinimizeWin),
        Minimize,
        getMinimizedWindows,
        getCurrentMinimizedWindows,
        setMinimized,
        setNotMinimized
    ) where

import XMonad
import qualified XMonad.StackSet as W
import XMonad.Layout.LayoutModifier
import XMonad.Layout.BoringWindows as BW
import XMonad.Util.WindowProperties (getProp32)
import Data.List
import qualified Data.Map as M
import qualified XMonad.Util.ExtensibleState as XS
import Data.Maybe
import Foreign.C.Types (CLong)

  > import XMonad . Layout . Minimize
  > myLayout = minimize ( Tall 1 ( 3/100 ) ( 1/2 ) ) ||| Full ||| etc ..
  >         , ( ( modm ,                xK_m      ) ,   )
  The first action will minimize the focused window , while the second one will restore
  " XMonad . Doc . Extending#Editing_key_bindings " .
  The module is designed to work together with " XMonad . Layout . BoringWindows " so
  documentation of " XMonad . Layout . BoringWindows " on how to modify your keybindings .

data Minimize a = Minimize [Window] (M.Map Window W.RationalRect) deriving ( Read, Show )
minimize :: LayoutClass l Window => l Window -> ModifiedLayout Minimize l Window
minimize = ModifiedLayout $ Minimize [] M.empty

data MinimizeMsg = MinimizeWin Window
                    | RestoreMinimizedWin Window
                    | RestoreNextMinimizedWin
                    | MinimizedSave
                    deriving (Typeable, Eq)
instance Message MinimizeMsg

minimizeWindow :: Window -> X ()
minimizeWindow w = sendMessage (MinimizeWin w) >> BW.focusDown

setMinimizedState :: Window -> Int -> (CLong -> [CLong] -> [CLong]) -> X ()
setMinimizedState win st f = do
    setWMState win st
    withDisplay $ \dpy -> do
        wm_state <- getAtom "_NET_WM_STATE"
        mini <- getAtom "_NET_WM_STATE_HIDDEN"
        wstate <- fromMaybe [] `fmap` getProp32 wm_state win
            fi_mini = fromIntegral mini
        io $ changeProperty32 dpy win wm_state ptype propModeReplace (f fi_mini wstate)

setMinimized :: Window -> X ()
setMinimized win = setMinimizedState win iconicState (:)

setNotMinimized :: Window -> X ()
setNotMinimized win = setMinimizedState win normalState delete

data MinimizedWindows = MinimizedWindows [Window] deriving (Read, Show, Typeable)
instance ExtensionClass MinimizedWindows where
    initialValue = MinimizedWindows []
    extensionType = PersistentExtension

getMinimizedWindows l = do
    res <- handleMessage l (SomeMessage MinimizedSave) `catchX` return Nothing
    case res of
         Just _ -> do
             MinimizedWindows wins <- XS.get
             return wins
         _ -> return []

getCurrentMinimizedWindows = gets (W.layout . W.workspace . W.current . windowset) >>= getMinimizedWindows

correctWins ls ws = filter (`elem` (W.allWindows ws)) ls

instance LayoutModifier Minimize Window where
    modifierDescription _ = "Minimize"

    modifyLayout (Minimize minimized _) wksp rect = do
        let stack = W.stack wksp
            filtStack = stack >>=W.filter (\w -> not (w `elem` minimized))
        runLayout (wksp {W.stack = filtStack}) rect

    handleMess ol@(Minimize minimized unfloated) m
        | Just (MinimizeWin w) <- fromMessage m, not (w `elem` minimized) = do
                setMinimized w
                ws <- gets windowset
                case M.lookup w (W.floating ws) of
                  Nothing -> return $ Just $ Minimize (w:minimized) unfloated
                  Just r -> do
                    modify (\s -> s { windowset = W.sink w ws})
                    return $ Just $ Minimize (w:minimized) (M.insert w r unfloated)
        | Just (RestoreMinimizedWin w) <- fromMessage m = do
            setNotMinimized w
            case M.lookup w unfloated of
              Nothing -> return $ Just $ Minimize (minimized \\ [w]) unfloated
              Just r -> do
                ws <- gets windowset
                modify (\s -> s { windowset = W.float w r ws})
                return $ Just $ Minimize (minimized \\ [w]) (M.delete w unfloated)
        | Just RestoreNextMinimizedWin <- fromMessage m = do
          ws <- gets windowset
          let cminimized = correctWins minimized ws
          if not (null cminimized)
            then case M.lookup (head cminimized) unfloated of
              Nothing -> do
                let w = head cminimized
                setNotMinimized w
                modify (\s -> s { windowset = W.focusWindow w ws})
                return $ Just $ Minimize (tail cminimized) unfloated
              Just r -> do
                let w = head cminimized
                setNotMinimized w
                modify (\s -> s { windowset = (W.focusWindow w . W.float w r) ws})
                return $ Just $ Minimize (tail cminimized) (M.delete w unfloated)
            else return Nothing
        | Just MinimizedSave <- fromMessage m = do
            ws <- gets windowset
            XS.put $ MinimizedWindows (correctWins minimized ws)
            return $ Just ol
        | Just BW.UpdateBoring <- fromMessage m = do
            ws <- gets (W.workspace . W.current . windowset)
            flip sendMessageWithNoRefresh ws $ BW.Replace "Minimize" minimized
            return Nothing
        | otherwise = return Nothing
