{-# LANGUAGE PatternGuards, ParallelListComp, DeriveDataTypeable, FlexibleInstances, FlexibleContexts, MultiParamTypeClasses, TypeSynonymInstances #-}------------------------------------------------------------------------------- |-- Module : XMonad.Layout.SubLayouts-- Copyright : (c) 2009 Adam Vogt-- License : BSD-style (see xmonad/LICENSE)---- Maintainer : vogt.adam@gmail.com-- Stability : unstable-- Portability : unportable---- A layout combinator that allows layouts to be nested.-------------------------------------------------------------------------------moduleXMonad.Layout.SubLayouts(-- * Usage-- $usagesubLayout,subTabbed,pushGroup,pullGroup,pushWindow,pullWindow,onGroup,toSubl,mergeDir,GroupMsg(..),Broadcast(..),defaultSublMap,-- * Todo-- $todo)whereimportXMonad.Layout.Decoration(Decoration,DefaultShrinker)importXMonad.Layout.LayoutModifier(LayoutModifier(handleMess,modifyLayout,redoLayout),ModifiedLayout(..))importXMonad.Layout.Simplest(Simplest(..))importXMonad.Layout.Tabbed(defaultTheme,shrinkText,TabbedDecoration,addTabs)importXMonad.Layout.WindowNavigation(Direction,Navigate(Apply))importXMonad.Util.Invisible(Invisible(..))importXMonadimportControl.Applicative((<$>))importControl.Arrow(Arrow(second,(&&&)))importControl.Monad(Monad(return),Functor(..),MonadPlus(mplus),(=<<),sequence,foldM,guard,when,join)importData.Function((.),($),flip,id,on)importData.List((++),foldr,filter,map,concatMap,elem,notElem,null,nubBy,(\\),find)importData.Maybe(Maybe(..),maybe,fromMaybe,listToMaybe,mapMaybe)importData.Traversable(sequenceA)importqualifiedXMonad.Layout.BoringWindowsasBimportqualifiedXMonad.StackSetasWimportqualifiedData.MapasMimportData.Map(Map)-- $todo-- 'subTabbed' works well, but it would be more uniform to avoid the use of-- addTabs, with the sublayout being Simplest (but-- 'XMonad.Layout.Tabbed.simpleTabbed' is this...). The only thing to be-- gained by fixing this issue is the ability to mix and match decoration-- styles. Better compatibility with some other layouts of which I am not-- aware could be another benefit.---- 'simpleTabbed' (and other decorated layouts) fail horibly when used as-- subLayouts:---- * decorations stick around: layout is run after being told to Hide---- * mouse events do not change focus: the group-ungroup does not respect-- the focus changes it wants?---- * sending ReleaseResources before running it makes xmonad very slow, and-- still leaves borders sticking around---- Issue 288: "XMonad.Layout.ResizableTile" assumes that its environment-- contains only the windows it is running: should sublayouts be run in a-- restricted environment that is then merged back?-- $usage-- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@:---- > import XMonad.Layout.SubLayouts-- > import XMonad.Layout.WindowNavigation---- Using BoringWindows is optional and it allows you to add a keybinding to-- skip over the non-visible windows.---- > import XMonad.Layout.BoringWindows---- Then edit your @layoutHook@ by adding the subTabbed layout modifier:---- > myLayouts = windowNavigation $ subTabbed $ boringWindows $-- > Tall 1 (3/100) (1/2) ||| etc..-- > main = xmonad defaultConfig { layoutHook = myLayouts }---- "XMonad.Layout.WindowNavigation" is used to specify which windows to merge,-- and it is not integrated into the modifier because it can be configured, and-- works best as the outer modifier.---- Then to your keybindings add:---- > , ((modMask .|. controlMask, xK_h), sendMessage $ pullGroup L)-- > , ((modMask .|. controlMask, xK_l), sendMessage $ pullGroup R)-- > , ((modMask .|. controlMask, xK_k), sendMessage $ pullGroup U)-- > , ((modMask .|. controlMask, xK_j), sendMessage $ pullGroup D)-- >-- > , ((modMask .|. controlMask, xK_m), withFocused (sendMessage . MergeAll))-- > , ((modMask .|. controlMask, xK_u), withFocused (sendMessage . UnMerge))-- >-- > , ((modMask .|. controlMask, xK_period), onGroup W.focusUp')-- > , ((modMask .|. controlMask, xK_comma), onGroup W.focusDown')---- These additional keybindings require the optional-- "XMonad.Layout.BoringWindows" layoutModifier. The focus will skip over the-- windows that are not focused in each sublayout.---- > , ((modMask, xK_j), focusDown)-- > , ((modMask, xK_k), focusUp)---- A 'submap' can be used to make modifying the sublayouts using 'onGroup' and-- 'toSubl' simpler:---- > ,((modm, xK_s), submap $ defaultSublMap conf)---- /NOTE:/ is there some reason that @asks config >>= submap . defaultSublMap@-- could not be used in the keybinding instead? It avoids having to explicitly-- pass the conf.---- For more detailed instructions, see:---- "XMonad.Doc.Extending#Editing_the_layout_hook"-- "XMonad.Doc.Extending#Adding_key_bindings"-- | The main layout modifier arguments:---- [@nextLayout@] When a new group is formed, use the layout @sl@ after-- skipping that number of layouts. Specify a finite list and groups that do-- not have a corresponding index get the first choice in @sls@---- [@sl@] The single layout given to be run as a sublayout.---- [@x@] The layout that determines the rectangles that the groups get.---- Ex. The second group is Tall, the third is Circle, all others are tabbed-- with:---- > myLayout = addTabs shrinkText defaultTheme-- > $ subLayout [0,1,2] (Simplest ||| Tall 1 0.2 0.5 ||| Circle)-- > $ Tall 1 0.2 0.5 ||| FullsubLayout::[Int]->subla->la->ModifiedLayout(Sublayoutsubl)lasubLayoutnextLayoutslx=ModifiedLayout(Sublayout(I[])(nextLayout,sl)[])x-- | 'subLayout' but use 'XMonad.Layout.Tabbed.addTabs' to add decorations.subTabbed::(Eqa,LayoutModifier(SublayoutSimplest)a,LayoutClassla)=>la->ModifiedLayout(DecorationTabbedDecorationDefaultShrinker)(ModifiedLayout(SublayoutSimplest)l)asubTabbedx=addTabsshrinkTextdefaultTheme$subLayout[]Simplestx-- | @defaultSublMap@ is an attempt to create a set of keybindings like the-- defaults ones but to be used as a 'submap' for sending messages to the-- sublayout.defaultSublMap::XConfigl->Map(KeyMask,KeySym)(X())defaultSublMap(XConfig{modMask=modm})=M.fromList[((modm,xK_space),toSublNextLayout),((modm,xK_j),onGroupW.focusDown'),((modm,xK_k),onGroupW.focusUp'),((modm,xK_h),toSublShrink),((modm,xK_l),toSublExpand),((modm,xK_Tab),onGroupW.focusDown'),((modm.|.shiftMask,xK_Tab),onGroupW.focusUp'),((modm,xK_m),onGroupfocusMaster'),((modm,xK_comma),toSubl$IncMasterN1),((modm,xK_period),toSubl$IncMasterN(-1)),((modm,xK_Return),onGroupswapMaster')]where-- should these go into XMonad.StackSet?focusMaster'st=let(f:fs)=W.integratestinW.Stackf[]fsswapMaster'(W.Stackfud)=W.Stackf[]$reverseu++ddataSublayoutla=Sublayout{delayMess::Invisible[](SomeMessage,a)-- ^ messages are handled when running the layout,-- not in the handleMessage, I'm not sure that this-- is necessary,def::([Int],la)-- ^ how many NextLayout messages to send to newly-- populated layouts. If there is no corresponding-- index, then don't send any.,subls::[(la,W.Stacka)]-- ^ The sublayouts and the stacks they manage}deriving(Read,Show)-- | Groups assumes this invariant:-- M.keys gs == map W.focus (M.elems gs) (ignoring order)-- All windows in the workspace are in the Map---- The keys are visible windows, the rest are hidden.---- This representation probably simplifies the internals of the modifier.typeGroupsa=Mapa(W.Stacka)-- | GroupMsg take window parameters to determine which group the action should-- be applied todataGroupMsga=UnMergea-- ^ free the focused window from its tab stack|UnMergeAlla-- ^ separate the focused group into singleton groups|Mergeaa-- ^ merge the first group into the second group|MergeAlla-- ^ make one large group, keeping a focused|WithGroup(W.Stacka->X(W.Stacka))a|SubMessageSomeMessagea-- ^ the sublayout with the given window will get the messagederiving(Typeable)-- | merge the window that would be focused by the function when applied to the-- W.Stack of all windows, with the current group removed. The given window-- should be focused by a sublayout. Example usage: @withFocused (sendMessage .-- mergeDir W.focusDown')@mergeDir::(W.StackWindow->W.StackWindow)->Window->GroupMsgWindowmergeDirfw=WithGroupgwwheregcs=doletonlyOthers=W.filter(`notElem`W.integratecs)flipwhenJust(sendMessage.Merge(W.focuscs).W.focus.f)=<<fmap(onlyOthers=<<)currentStackreturncsdataBroadcast=BroadcastSomeMessage-- ^ send a message to all sublayoutsderiving(Typeable)instanceMessageBroadcastinstanceTypeablea=>Message(GroupMsga)-- | pullGroup, pushGroup allow you to merge windows or groups inheriting the-- position of the current window (pull) or the other window (push).pullGroup::Direction->NavigatepullGroup=mergeNav(\oc->sendMessage$Mergeoc)pullWindow::Direction->NavigatepullWindow=mergeNav(\oc->sendMessage(UnMergeo)>>sendMessage(Mergeoc))pushGroup::Direction->NavigatepushGroup=mergeNav(\oc->sendMessage$Mergeco)pushWindow::Direction->NavigatepushWindow=mergeNav(\oc->sendMessage(UnMergec)>>sendMessage(Mergeco))mergeNav::(Window->Window->X())->Direction->NavigatemergeNavf=Apply(\o->withFocused(fo))-- | Apply a function on the stack belonging to the currently focused group. It-- works for rearranging windows and for changing focus.onGroup::(W.StackWindow->W.StackWindow)->X()onGroupf=withFocused(sendMessage.WithGroup(return.f))-- | Send a message to the currently focused sublayout.toSubl::(Messagea)=>a->X()toSublm=withFocused(sendMessage.SubMessage(SomeMessagem))instance(Read(lWindow),Show(lWindow),LayoutClasslWindow)=>LayoutModifier(Sublayoutl)WindowwheremodifyLayout(Sublayout{subls=osls})(W.Workspaceilast)r=doletgs'=updateGroupst$toGroupsoslsst'=W.filter(`elem`M.keysgs')=<<stupdateWsgs'runLayout(W.Workspaceilast')rredoLayout(Sublayout{delayMess=Ims,def=defl,subls=osls})_rstarrs=doletgs'=updateGroupst$toGroupsoslssls<-fromGroupsdeflstgs'oslsletnewL::LayoutClasslWindow=>Rectangle->WorkspaceId->(lWindow,Bool)->(Maybe(W.StackWindow))->X([(Window,Rectangle)],lWindow)newLrectn(ol,mess)sst=dolethandlel(y,_)|mess=fromMaybel<$>handleMessagely|otherwise=returnlkms=filter((`elem`M.keysgs').snd)msnl<-foldMhandleol$filter((`elem`W.integrate'sst).snd)kmsfmap(fromMaybenl)<$>runLayout(W.Workspacennlsst)rect(urls,ssts)=unzip[(newLgrilsst,sst)|l<-map(second$constTrue)sls|i<-mapshow[0::Int..]|(k,gr)<-arrs,letsst=M.lookupkgs']arrs'<-sequenceurlssls'<-return.Sublayout(I[])defl<$>fromGroupsdeflstgs'[(l,s)|(_,l)<-arrs'|(Justs)<-ssts]return(concatMapfstarrs',sls')handleMess(Sublayout(Ims)deflsls)m|Just(SubMessagesmw)<-fromMessagem=return$Just$Sublayout(I((sm,w):ms))deflsls|Just(Broadcastsm)<-fromMessagem=doms'<-fmap(zip(repeatsm).W.integrate')currentStackreturn$ifnullms'thenNothingelseJust$Sublayout(I$ms'++ms)deflsls|JustB.UpdateBoring<-fromMessagem=doletbs=concatMapunfocused$M.elemsgsws<-gets(W.workspace.W.current.windowset)flipsendMessageWithNoRefreshws$B.Replace"Sublayouts"bsreturnNothing|Just(WithGroupfw)<-fromMessagem,Justg<-M.lookupwgs=dog'<-fgletgs'=M.insert(W.focusg')g'$M.delete(W.focusg)gswhen(gs'/=gs)$updateWsgs'when(w/=W.focusg')$windows(W.focusWindow$W.focusg')returnNothing|Just(MergeAllw)<-fromMessagem=letgs'=fmap(M.singletonw)$(focusWindow'w=<<)$W.differentiate$concatMapW.integrate$M.elemsgsinmaybe(returnNothing)fgsgs'|Just(UnMergeAllw)<-fromMessagem=letws=concatMapW.integrate$M.elemsgs_=w::WindowmkSingletonf=M.singletonf(W.Stackf[][])infgs$M.unions$mapmkSingletonws|Just(Mergexy)<-fromMessagem,letfindGrpz=mplus(M.lookupzgs)$listToMaybe$M.elems$M.filter((z`elem`).W.integrate)gs,Just(W.Stack_xbxn)<-findGrpx,Justyst<-findGrpy=letzs=W.Stackxxb(xn++W.integrateyst)infgs$M.update(\_->Justzs)x$M.deleteygs|Just(UnMergex)<-fromMessagem=fgs.M.fromList.map(W.focus&&&id).M.elems$M.mapMaybe(W.filter(x/=))gs|otherwise=fmapjoin$sequenceA$catchLayoutMess<$>fromMessagemwheregs=toGroupsslsfgsgs'=dost<-currentStackJust.Sublayout(Ims)defl<$>fromGroupsdeflstgs'sls-- catchLayoutMess :: LayoutMessages -> X (Maybe (Sublayout l Window))-- This l must be the same as from the instance head,-- -XScopedTypeVariables should bring it into scope, but we are-- trying to avoid warnings with ghc-6.8.2 and avoid CPPcatchLayoutMessx=doletm'=x`asTypeOf`(undefined::LayoutMessages)ms'<-zip(repeat$SomeMessagem').W.integrate'<$>currentStackreturn$doguard$not$nullms'Just$Sublayout(I$ms'++ms)deflslscurrentStack::X(Maybe(W.StackWindow))currentStack=gets(W.stack.W.workspace.W.current.windowset)-- | update Group to follow changes in the workspaceupdateGroup::Orda=>Maybe(W.Stacka)->Groupsa->GroupsaupdateGroupmstgs=letflatten=concatMapW.integrate.M.elemsnews=W.integrate'mst\\flattengsdeads=flattengs\\W.integrate'mstuniNew=M.union(M.fromList$map(\n->(n,singlen))news)singlex=W.Stackx[][]-- pass through a list to update/remove keysremDead=M.fromList.map(\w->(W.focusw,w)).mapMaybe(W.filter(`notElem`deads)).M.elems-- update the current tab group's order and focusfollowFocushs=fromMaybehs$dof'<-W.focus`fmap`mstxs<-find(elemf'.W.integrate)$M.elemshsxs'<-W.filter(`elem`W.integratexs)=<<mstreturn$M.insertf'xs'$M.delete(W.focusxs)hsinremDead$uniNew$followFocusgs-- | rearrange the windowset to put the groups of tabs next to eachother, so-- that the stack of tabs stays put.updateWs::GroupsWindow->X()updateWs=windowsMaybe.updateWs'updateWs'::GroupsWindow->WindowSet->MaybeWindowSetupdateWs'gsws=dof<-W.peekwsletw=W.indexwsnes=concatMapW.integrate$mapMaybe(flipM.lookupgs)wws'=W.focusWindowf$foldrW.insertUp(foldrW.delete'wsnes)nesguard$W.indexws'/=W.indexwsreturnws'-- | focusWindow'. focus an element of a stack, is Nothing if that element is-- absent. See also 'W.focusWindow'focusWindow'::(Eqa)=>a->W.Stacka->Maybe(W.Stacka)focusWindow'wst=doguard$not$null$filter(w==)$W.integratestifW.focusst==wthenJuststelsefocusWindow'w$W.focusDown'st-- update only when JustwindowsMaybe::(WindowSet->MaybeWindowSet)->X()windowsMaybef=doxst<-getws<-getswindowsetletupfws=putxst{windowset=fws}maybe(return())up$fwsunfocused::W.Stacka->[a]unfocusedx=W.upx++W.downxtoGroups::(Orda)=>[(a1,W.Stacka)]->Mapa(W.Stacka)toGroupsws=M.fromList.map(W.focus&&&id).nubBy(on(==)W.focus)$mapsndws-- | restore the default layout for each group. It needs the X monad to switch-- the default layout to a specific one (handleMessage NextLayout)fromGroups::(LayoutClasslayouta,Ordk)=>([Int],layouta)->Maybe(W.Stackk)->Groupsk->[(layouta,b)]->X[(layouta,W.Stackk)]fromGroups(skips,defl)stgssls=dodefls<-mapM(iterateMnextLdefl!!)skipsreturn$fromGroups'defldeflsstgs(mapfstsls)wherenextLl=fromMaybel<$>handleMessagel(SomeMessageNextLayout)iterateMf=iterate(>>=f).returnfromGroups'::(Ordk)=>a->[a]->Maybe(W.Stackk)->Groupsk->[a]->[(a,W.Stackk)]fromGroups'defldeflsstgssls=[fromMaybe2(dl,singlew)(l,M.lookupwgs)|l<-mapJustsls++repeatNothing|dl<-defls++repeatdefl|w<-W.integrate'$W.filter(`notElem`unfocs)=<<st]whereunfocs=unfocused=<<M.elemsgssinglew=W.Stackw[][]fromMaybe2(a,b)(x,y)=(fromMaybeax,fromMaybeby)