{-# OPTIONS_GHC -fno-warn-name-shadowing -fno-warn-unused-binds #-}{-# LANGUAGE StandaloneDeriving, FlexibleContexts, DeriveDataTypeable
, UndecidableInstances, FlexibleInstances, MultiParamTypeClasses
, PatternGuards, Rank2Types, TypeSynonymInstances #-}------------------------------------------------------------------------------- |-- Module : XMonad.Layout.Groups-- Copyright : Quentin Moser <moserq@gmail.com>-- License : BSD-style (see LICENSE)---- Maintainer : orphaned-- Stability : unstable-- Portability : unportable---- Two-level layout with windows split in individual layout groups,-- themselves managed by a user-provided layout.-------------------------------------------------------------------------------moduleXMonad.Layout.Groups(-- * Usage-- $usage-- * Creationgroup-- * Messages,GroupsMessage(..),ModifySpec-- ** Useful 'ModifySpec's,swapUp,swapDown,swapMaster,focusUp,focusDown,focusMaster,swapGroupUp,swapGroupDown,swapGroupMaster,focusGroupUp,focusGroupDown,focusGroupMaster,moveToGroupUp,moveToGroupDown,moveToNewGroupUp,moveToNewGroupDown,splitGroup-- * Types,Groups,Group(..),onZipper,onLayout,WithID,sameID)whereimportXMonadimportqualifiedXMonad.StackSetasWimportXMonad.Util.StackimportData.Maybe(isJust,isNothing,fromMaybe,catMaybes,fromJust)importData.List((\\))importControl.Arrow((>>>))importControl.Applicative((<$>))importControl.Monad(forM)-- $usage-- This module provides a layout combinator that allows you-- to manage your windows in independent groups. You can provide-- both the layout with which to arrange the windows inside each-- group, and the layout with which the groups themselves will-- be arranged on the screen.---- The "XMonad.Layout.Groups.Examples" and "XMonad.Layout.Groups.Wmii"-- modules contain examples of layouts that can be defined with this-- combinator. They're also the recommended starting point-- if you are a beginner and looking for something you can use easily.---- One thing to note is that 'Groups'-based layout have their own-- notion of the order of windows, which is completely separate-- from XMonad's. For this reason, operations like 'XMonad.StackSet.SwapUp'-- will have no visible effect, and those like 'XMonad.StackSet.focusUp'-- will focus the windows in an unpredictable order. For a better way of-- rearranging windows and moving focus in such a layout, see the-- example 'ModifySpec's (to be passed to the 'Modify' message) provided-- by this module.---- If you use both 'Groups'-based and other layouts, The "XMonad.Layout.Groups.Helpers"-- module provides actions that can work correctly with both, defined using-- functions from "XMonad.Actions.MessageFeedback".-- | Create a 'Groups' layout.---- Note that the second parameter (the layout for arranging the-- groups) is not used on 'Windows', but on 'Group's. For this-- reason, you can only use layouts that don't specifically-- need to manage 'Window's. This is obvious, when you think-- about it.group::lWindow->l2(GrouplWindow)->Groupsll2Windowgroupll2=Groupsll2startingGroups(U10)wherestartingGroups=fromJust$singletonZ$G(ID(U00)l)emptyZ-- * Stuff with unique keysdataUniq=UIntegerIntegerderiving(Eq,Show,Read)-- | From a seed, generate an infinite list of keys and a new-- seed. All keys generated with this method will be different-- provided you don't use 'gen' again with a key from the list.-- (if you need to do that, see 'split' instead)gen::Uniq->(Uniq,[Uniq])gen(Ui1i2)=(U(i1+1)i2,zipWithU(repeati1)[i2..])-- | Split an infinite list into two. I ended up not-- needing this, but let's keep it just in case.-- split :: [a] -> ([a], [a])-- split as = snd $ foldr step (True, ([], [])) as-- where step a (True, (as1, as2)) = (False, (a:as1, as2))-- step a (False, (as1, as2)) = (True, (as1, a:as2))-- | Add a unique identity to a layout so we can-- follow it around.dataWithIDla=ID{getID::Uniq,unID::(la)}deriving(Show,Read)-- | Compare the ids of two 'WithID' valuessameID::WithIDla->WithIDla->BoolsameID(IDid1_)(IDid2_)=id1==id2instanceEq(WithIDla)whereIDid1_==IDid2_=id1==id2instanceLayoutClassla=>LayoutClass(WithIDl)awhererunLayoutws@W.Workspace{W.layout=IDidl}r=do(placements,ml')<-fliprunLayoutrws{W.layout=l}return(placements,IDid<$>ml')handleMessage(IDidl)sm=doml'<-handleMessagelsmreturn$IDid<$>ml'description(ID_l)=descriptionl-- * The 'Groups' layout-- ** Datatypes-- | A group of windows and its layout algorithm.dataGroupla=G{gLayout::WithIDla,gZipper::Zippera}deriving(Show,Read,Eq)onLayout::(WithIDla->WithIDla)->Groupla->GrouplaonLayoutfg=g{gLayout=f$gLayoutg}onZipper::(Zippera->Zippera)->Groupla->GrouplaonZipperfg=g{gZipper=f$gZipperg}-- | The type of our layouts.dataGroupsll2a=Groups{-- | The starting layout for new groupsbaseLayout::la-- | The layout for placing each group on the screen,partitioner::l2(Groupla)-- | The window groups,groups::W.Stack(Groupla)-- | A seed for generating unique ids,seed::Uniq}derivinginstance(Showa,Show(la),Show(l2(Groupla)))=>Show(Groupsll2a)derivinginstance(Reada,Read(la),Read(l2(Groupla)))=>Read(Groupsll2a)-- | Messages accepted by 'Groups'-based layouts.-- All other messages are forwarded to the layout of the currently-- focused subgroup (as if they had been wrapped in 'ToFocused').dataGroupsMessage=ToEnclosingSomeMessage-- ^ Send a message to the enclosing layout-- (the one that places the groups themselves)|ToGroupIntSomeMessage-- ^ Send a message to the layout for nth group-- (starting at 0)|ToFocusedSomeMessage-- ^ Send a message to the layout for the focused-- group|ToAllSomeMessage-- ^ Send a message to all the sub-layouts|Refocus-- ^ Refocus the window which should be focused according-- to the layout.|ModifyModifySpec-- ^ Modify the ordering\/grouping\/focusing-- of windows according to a 'ModifySpec'derivingTypeableinstanceShowGroupsMessagewhereshow(ToEnclosing_)="ToEnclosing {...}"show(ToGroupi_)="ToGroup "++showi++" {...}"show(ToFocused_)="ToFocused {...}"show(ToAll_)="ToAll {...}"showRefocus="Refocus"show(Modify_)="Modify {...}"instanceMessageGroupsMessagemodifyGroups::(Zipper(Groupla)->Zipper(Groupla))->Groupsll2a->Groupsll2amodifyGroupsfg=let(seed',id:_)=gen(seedg)defaultGroups=fromJust$singletonZ$G(IDid$baseLayoutg)emptyZing{groups=fromMaybedefaultGroups.f.Just$groupsg,seed=seed'}-- ** Readaptation-- | Adapt our groups to a new stack.-- This algorithm handles window additions and deletions correctly,-- ignores changes in window ordering, and tries to react to any-- other stack changes as gracefully as possible.readapt::Eqa=>Zippera->Groupsll2a->Groupsll2areadaptzg=letmf=getFocusZz(seed',id:_)=gen$seedgg'=g{seed=seed'}inflipmodifyGroupsg'$mapZ_(onZipper$removeDeletedz)>>>filterKeepLast(isJust.gZipper)>>>findNewWindows(W.integrate'z)>>>addWindows(IDid$baseLayoutg)>>>focusGroupmf>>>onFocusedZ(onZipper$focusWindowmf)wherefilterKeepLast_Nothing=NothingfilterKeepLastfz@(Justs)=maybe(singletonZ$W.focuss)Just$filterZ_fz-- | Remove the windows from a group which are no longer present in-- the stack.removeDeleted::Eqa=>Zippera->Zippera->ZipperaremoveDeletedz=filterZ_(flipelemZz)-- | Identify the windows not already in a group.findNewWindows::Eqa=>[a]->Zipper(Groupla)->(Zipper(Groupla),[a])findNewWindowsasgs=(gs,foldrZ_removePresentasgs)whereremovePresentgas'=filter(not.flipelemZ(gZipperg))as'-- | Add windows to the focused group. If you need to create one,-- use the given layout and an id from the given list.addWindows::WithIDla->(Zipper(Groupla),[a])->Zipper(Groupla)addWindowsl(Nothing,as)=singletonZ$Gl(W.differentiateas)addWindows_(z,as)=onFocusedZ(onZipperadd)zwhereaddz=foldl(flipinsertUpZ)zas-- | Focus the group containing the given windowfocusGroup::Eqa=>Maybea->Zipper(Groupla)->Zipper(Groupla)focusGroupNothing=idfocusGroup(Justa)=fromTags.map(tagBy$elemZa.gZipper).W.integrate'-- | Focus the given windowfocusWindow::Eqa=>Maybea->Zippera->ZipperafocusWindowNothing=idfocusWindow(Justa)=fromTags.map(tagBy(==a)).W.integrate'-- * Interface-- ** Layout instanceinstance(LayoutClasslWindow,LayoutClassl2(GrouplWindow))=>LayoutClass(Groupsll2)Windowwheredescription(Groups_pgs_)=s1++" by "++s2wheres1=description$gLayout$W.focusgss2=descriptionprunLayoutws@(W.Workspace__lz)r=letl=readaptz_lindo(areas,mpart')<-runLayoutws{W.layout=partitionerl,W.stack=Just$groupsl}rresults<-forMareas$\(g,r')->runLayoutws{W.layout=gLayoutg,W.stack=gZipperg}r'lethidden=mapgLayout(W.integrate$groups_l)\\map(gLayout.fst)areashidden'<-mapM(fliphandleMessage$SomeMessageHide)hiddenletplacements=concatMapfstresultsnewL=justMakeNewlmpart'(mapsndresults++hidden')return$(placements,newL)handleMessagel@(Groups_p__)sm|Just(ToEnclosingsm')<-fromMessagesm=domp'<-handleMessagepsm'return$maybeMakeNewlmp'[]handleMessagel@(Groups_pgs_)sm|Just(ToAllsm')<-fromMessagesm=domp'<-handleMessagepsm'mg's<-mapZM_(handlesm')$Justgsreturn$maybeMakeNewlmp'$W.integrate'mg'swherehandlesm(Gl_)=handleMessagelsmhandleMessagelsm|Justa<-fromMessagesm=let_rightType=a==Hide-- Is there a better-looking way-- of doing this?inhandleMessagel$SomeMessage$ToAllsmhandleMessagel@(Groups__z_)sm=casefromMessagesmofJust(ToFocusedsm')->domg's<-W.integrate'<$>handleOnFocusedsm'zreturn$maybeMakeNewlNothingmg'sJust(ToGroupism')->domg's<-handleOnIndexism'zreturn$maybeMakeNewlNothingmg'sJust(Modifyspec)->caseapplySpecspeclofJustl'->refocusl'>>return(Justl')Nothing->return$JustlJustRefocus->refocusl>>return(Justl)Just_->returnNothingNothing->handleMessagel$SomeMessage(ToFocusedsm)wherehandleOnFocusedsmz=mapZMstep$JustzwherestepTrue(Gl_)=handleMessagelsmstepFalse_=returnNothinghandleOnIndexismz=mapMstep$zip[0..]$W.integratezwherestep(j,(Gl_))|i==j=handleMessagelsmstep_=returnNothingjustMakeNew::Groupsll2a->Maybe(l2(Groupla))->[Maybe(WithIDla)]->Maybe(Groupsll2a)justMakeNewgmpart'ml's=Justg{partitioner=fromMaybe(partitionerg)mpart',groups=combine(groupsg)ml's}wherecombinezml's=lettable=map(\(IDida)->(id,a))$catMaybesml'sinflipmapS_z$\(G(IDidl)ws)->caselookupidtableofNothing->G(IDidl)wsJustl'->G(IDidl')wsmapS_f=fromJust.mapZ_f.JustmaybeMakeNew::Groupsll2a->Maybe(l2(Groupla))->[Maybe(WithIDla)]->Maybe(Groupsll2a)maybeMakeNew_Nothingml's|allisNothingml's=NothingmaybeMakeNewgmpart'ml's=justMakeNewgmpart'ml'srefocus::Groupsll2Window->X()refocusg=casegetFocusZ$gZipper$W.focus$groupsgofJustw->focuswNothing->return()-- ** ModifySpec type-- | Type of functions describing modifications to a 'Groups' layout. They-- are transformations on 'Zipper's of groups.---- Things you shouldn't do:---- * Forge new windows (they will be ignored)---- * Duplicate windows (whatever happens is your problem)---- * Remove windows (they will be added again)---- * Duplicate layouts (only one will be kept, the rest will-- get the base layout)---- Note that 'ModifySpec' is a rank-2 type (indicating that 'ModifySpec's must-- be polymorphic in the layout type), so if you define functions taking-- 'ModifySpec's as arguments, or returning them, you'll need to write a type-- signature and add @{-# LANGUAGE Rank2Types #-}@ at the beginningtypeModifySpec=foralll.WithIDlWindow->Zipper(GrouplWindow)->Zipper(GrouplWindow)-- | Apply a ModifySpec.applySpec::ModifySpec->Groupsll2Window->Maybe(Groupsll2Window)applySpecfg=let(seed',id:ids)=gen$seedgg'=flipmodifyGroupsg$f(IDid$baseLayoutg)>>>toTags>>>foldrreID((ids,[]),[])>>>snd>>>fromTagsincasegroupsg==groupsg'ofTrue->NothingFalse->Justg'{seed=seed'}wherereIDeg((id:ids,seen),egs)=letmyID=getID$gLayout$fromEegincaseelemmyIDseenofFalse->((id:ids,myID:seen),eg:egs)True->((ids,seen),mapE_(setIDid)eg:egs)wheresetIDid(G(ID__)z)=G(IDid$baseLayoutg)zreID_(([],_),_)=undefined-- The list of ids is infinite-- ** Misc. ModifySpecs-- | helperonFocused::(ZipperWindow->ZipperWindow)->ModifySpeconFocusedf_gs=onFocusedZ(onZipperf)gs-- | Swap the focused window with the previous one.swapUp::ModifySpecswapUp=onFocusedswapUpZ-- | Swap the focused window with the next one.swapDown::ModifySpecswapDown=onFocusedswapDownZ-- | Swap the focused window with the (group's) master-- window.swapMaster::ModifySpecswapMaster=onFocusedswapMasterZ-- | Swap the focused group with the previous one.swapGroupUp::ModifySpecswapGroupUp_=swapUpZ-- | Swap the focused group with the next one.swapGroupDown::ModifySpecswapGroupDown_=swapDownZ-- | Swap the focused group with the master group.swapGroupMaster::ModifySpecswapGroupMaster_=swapMasterZ-- | Move focus to the previous window in the group.focusUp::ModifySpecfocusUp=onFocusedfocusUpZ-- | Move focus to the next window in the group.focusDown::ModifySpecfocusDown=onFocusedfocusDownZ-- | Move focus to the group's master window.focusMaster::ModifySpecfocusMaster=onFocusedfocusMasterZ-- | Move focus to the previous group.focusGroupUp::ModifySpecfocusGroupUp_=focusUpZ-- | Move focus to the next group.focusGroupDown::ModifySpecfocusGroupDown_=focusDownZ-- | Move focus to the master group.focusGroupMaster::ModifySpecfocusGroupMaster_=focusMasterZ-- | helper_removeFocused::W.Stacka->(a,Zippera)_removeFocused(W.Stackf(u:up)down)=(f,Just$W.Stackuupdown)_removeFocused(W.Stackf[](d:down))=(f,Just$W.Stackd[]down)_removeFocused(W.Stackf[][])=(f,Nothing)-- helper_moveToNewGroup::WithIDlWindow->W.Stack(GrouplWindow)->(GrouplWindow->Zipper(GrouplWindow)->Zipper(GrouplWindow))->Zipper(GrouplWindow)_moveToNewGroupl0sinsertX|Gl(Justf)<-W.focuss=let(w,f')=_removeFocusedfs'=s{W.focus=Glf'}ininsertX(Gl0$singletonZw)$Justs'_moveToNewGroup_s_=Justs-- | Move the focused window to a new group before the current one.moveToNewGroupUp::ModifySpecmoveToNewGroupUp_Nothing=NothingmoveToNewGroupUpl0(Justs)=_moveToNewGroupl0sinsertUpZ-- | Move the focused window to a new group after the current one.moveToNewGroupDown::ModifySpecmoveToNewGroupDown_Nothing=NothingmoveToNewGroupDownl0(Justs)=_moveToNewGroupl0sinsertDownZ-- | Move the focused window to the previous group.-- If 'True', when in the first group, wrap around to the last one.-- If 'False', create a new group before it.moveToGroupUp::Bool->ModifySpecmoveToGroupUp__Nothing=NothingmoveToGroupUpFalsel0(Justs)=ifnull(W.ups)thenmoveToNewGroupUpl0(Justs)elsemoveToGroupUpTruel0(Justs)moveToGroupUpTrue_(Justs@(W.Stack_[][]))=JustsmoveToGroupUpTrue_(Justs@(W.Stack(Gl(Justf))__))=let(w,f')=_removeFocusedfinonFocusedZ(onZipper$insertUpZw)$focusUpZ$Justs{W.focus=Glf'}moveToGroupUpTrue_gs=gs-- | Move the focused window to the next group.-- If 'True', when in the last group, wrap around to the first one.-- If 'False', create a new group after it.moveToGroupDown::Bool->ModifySpecmoveToGroupDown__Nothing=NothingmoveToGroupDownFalsel0(Justs)=ifnull(W.downs)thenmoveToNewGroupDownl0(Justs)elsemoveToGroupDownTruel0(Justs)moveToGroupDownTrue_(Justs@(W.Stack_[][]))=JustsmoveToGroupDownTrue_(Justs@(W.Stack(Gl(Justf))__))=let(w,f')=_removeFocusedfinonFocusedZ(onZipper$insertUpZw)$focusDownZ$Justs{W.focus=Glf'}moveToGroupDownTrue_gs=gs-- | Split the focused group into two at the position of the focused window (below it,-- unless it's the last window - in that case, above it).splitGroup::ModifySpecsplitGroup_Nothing=NothingsplitGroupl0z@(Justs)|Gl(Justws)<-W.focuss=casewsofW.Stack_[][]->zW.Stackf(u:up)[]->letg1=Gl$Just$W.Stackf[][]g2=Gl0$Just$W.Stackuup[]ininsertDownZg1$onFocusedZ(constg2)zW.Stackfup(d:down)->letg1=Gl$Just$W.Stackfup[]g2=Gl0$Just$W.Stackd[]downininsertUpZg1$onFocusedZ(constg2)zsplitGroup__=Nothing