-- Author: Andy Stewart -- Maintainer: Andy Stewart -- -- Copyright (C) 2010 ~ 2011 Andy Stewart, all rights reserved. -- -- This program is free software: you can redistribute it and/or modify -- it under the terms of the GNU General Public License as published by -- the Free Software Foundation, either version 3 of the License, or -- any later version. -- -- This program is distributed in the hope that it will be useful, -- but WITHOUT ANY WARRANTY; without even the implied warranty of -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -- GNU General Public License for more details. -- -- You should have received a copy of the GNU General Public License -- along with this program. If not, see . {-# LANGUAGE ExistentialQuantification, DeriveDataTypeable, TypeSynonymInstances, RankNTypes, FlexibleInstances #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} module Manatee.Action.Basic where import Control.Applicative hiding (empty) import Control.Concurrent.STM.TVar import Control.Monad.State hiding (get) import DBus.Client hiding (Signal) import Data.List (partition) import Data.Text.Lazy (Text) import Graphics.UI.Gtk hiding (Action, Frame, Window, layoutPath) import Manatee.Action.BufferList import Manatee.Action.Tabbar import Manatee.Core.Config import Manatee.Core.DBus import Manatee.Core.Types import Manatee.Toolkit.Data.ListZipper hiding (length, delete, get) import Manatee.Toolkit.Data.SetList import Manatee.Toolkit.General.FilePath import Manatee.Toolkit.General.List import Manatee.Toolkit.General.Maybe import Manatee.Toolkit.General.Process import Manatee.Toolkit.General.STM import Manatee.Toolkit.General.Seq import Manatee.Toolkit.General.Set hiding (mapM) import Manatee.Toolkit.General.State import Manatee.Toolkit.Gtk.Container import Manatee.Toolkit.Gtk.Gtk import Manatee.Toolkit.Widget.NotebookTab import Manatee.Types import Manatee.UI.FocusNotifier import Manatee.UI.Frame import Manatee.UI.UIFrame import Manatee.UI.Window hiding (windowNew) import Manatee.UI.WindowNode import System.Directory import System.FilePath import System.Posix.Types (ProcessID) import qualified Data.Foldable as F import qualified Data.Map as M import qualified Data.Set as Set import qualified Graphics.UI.Gtk as Gtk import qualified Manatee.Toolkit.Data.ListZipper as LZ type PageActionInputArgs = (Window, (TVar SignalBoxList, (TVar PageId, TVar SignalBoxId))) -- | Run action. runAction :: Environment -> Action -> IO () runAction env (Action {actionFun = fun}) = envGet env >>= fun >>= envPut env (==>) :: forall a b . (ActionInputArgs a, ActionOutputArgs b) => Text -> (a -> IO b) -> (Text, Action) key ==> command = (key, Action command) -- | Build socket frame. socketFrameNew :: IO Gtk.Frame socketFrameNew = frameNewWithShadowType Nothing -- | Create new signal box. signalBoxNew :: UIFrame -> WindowId -> TVar SignalBoxId -> TVar SignalBoxList -> IO SignalBox signalBoxNew uiFrame windowId signalBoxCounter signalBoxList = do -- Ticker signal box counter. signalBoxId <- tickTVarIO signalBoxCounter -- Create signal box. let signalBox = SignalBox signalBoxId uiFrame windowId -- Add new SignalBox to list. runTVarStateT signalBoxList $ put . Set.insert signalBox return signalBox -- | Clone tabs. cloneTabs :: Window -> Client -> TVar Tabbar -> TVar SignalBoxList -> TVar SignalBoxId -> [(PageModeName, ProcessID, PageId)] -> IO () cloneTabs window client tabbarTVar signalBoxList sId = mapM_ (cloneTab window client tabbarTVar signalBoxList sId) -- | Clone tab. cloneTab :: Window -> Client -> TVar Tabbar -> TVar SignalBoxList -> TVar SignalBoxId -> (PageModeName, ProcessID, PageId) -> IO () cloneTab window client tabbarTVar signalBoxList sId (modeName, processId, pageId) = do let windowId = windowGetId window notebook = windowNotebook window -- Create new socket frame. uiFrame <- uiFrameStick notebook -- Create signal box. signalBox <- signalBoxNew uiFrame windowId sId signalBoxList let sId = signalBoxId signalBox -- Add page id information to tabbar, -- then will replace other information after render page create. -- And make sure render page will insert at correct place when call 'daemonHandleNewPageConfirm'. modifyTVarIO tabbarTVar (tabbarAddTab windowId modeName (Tab 0 pageId sId 0 0 uiFrame)) -- Send `CloneRenderPage` signal. mkRenderSignal client processId CloneRenderPage (CloneRenderPageArgs pageId sId) -- | Synchronization tab name. syncTabName :: Environment -> WindowId -> IO () syncTabName env windowId = do (tabbar, BufferList bufferList) <- envGet env tabbarGetTabInfo tabbar windowId ?>= \ (modeName, tabSeq) -> M.lookup modeName bufferList ?>= \ seqBuffer -> do let nameList = map bufferName $ F.toList seqBuffer zipWithIndexM_ nameList $ \name index -> maybeIndex tabSeq index ?>= \tab -> notebookTabSetName (uiFrameNotebookTab $ tabUIFrame tab) name -- | Get current uiFrame. getCurrentUIFrame :: Environment -> IO (Maybe UIFrame) getCurrentUIFrame env = getCurrentTab env >?>=> \ tab -> return $ Just (tabUIFrame tab) -- | Get page mode name of specify window. getWindowPageModeName :: Environment -> Window -> IO (Maybe PageModeName) getWindowPageModeName env window = do tabbar <- envGet env return $ tabbarGetPageModeName tabbar (windowGetId window) -- | Get current tab. getCurrentTab :: Environment -> IO (Maybe Tab) getCurrentTab env = do (tabbar, window) <- envGet env tabbarGetTabSeq tabbar (windowGetId window) ?>=> \ tabSeq -> do currentPageIndex <- notebookGetCurrentPage (windowNotebook window) return $ maybeIndex tabSeq currentPageIndex -- | Get next window. getNextWindow :: WindowList -> Maybe Window getNextWindow windowList | LZ.length windowList <= 1 = Nothing | otherwise = LZ.getRightCircular windowList -- | Action in next window. withNextWindow :: Environment -> (Window -> IO ()) -> IO () withNextWindow env action = do windowList <- envGet env case getNextWindow windowList of Just win -> action win Nothing -> putStrLn "Just current window exist." -- | Focus current tab.. focusCurrentTab :: Environment -> IO () focusCurrentTab env = do client <- envGet env -- Send `FocusRenderPage` signal to focus page. getCurrentTab env >?>= \ Tab {tabProcessId = processId ,tabPlugId = plugId} -> mkRenderSignal client processId FocusRenderPage (FocusRenderPageArgs plugId) -- | Update current tab. updateConfigure :: Environment -> IO () updateConfigure env = do client <- envGet env -- Send `UpdateConfig` signal to focus tab. getCurrentTab env >?>= \ Tab {tabProcessId = processId} -> mkRenderSignal client processId UpdateConfig UpdateConfigArgs -- | Install current tab. installConfigure :: Environment -> IO () installConfigure env = do client <- envGet env -- Send `InstallConfig` signal to focus tab. getCurrentTab env >?>= \ Tab {tabProcessId = processId} -> mkRenderSignal client processId InstallConfig InstallConfigArgs -- | Remove tabs match window id. removeTabs :: Tabbar -> Client -> Window -> IO Tabbar removeTabs (Tabbar tabbar) client window = do let windowId = windowGetId window notebook = windowNotebook window -- Remove all tab widget from notebook. containerRemoveAll notebook -- Send tab destroy signal to child process. forM_ (tabbarGetTabList windowId (Tabbar tabbar)) $ \ Tab {tabProcessId = processId ,tabPlugId = plugId} -> mkRenderSignal client processId DestroyRenderPage (DestroyRenderPageArgs plugId) -- Return new tabbar that remove all tabs match window id. return $ tabbarRemoveTabs windowId (Tabbar tabbar) -- | Exit all render processes. exitAllRenderProcess :: Environment -> IO () exitAllRenderProcess env = do (client, bufferListTVar) <- envGet env modifyTVarIOM bufferListTVar $ \ (BufferList bufferList) -> do -- Send dbus signal `ExitRenderProcess` to all render processes. forM_ (M.toList bufferList) $ \ (_, bufferSeq) -> forM_ (F.toList bufferSeq) $ \ Buffer {bufferProcessId = processId ,bufferPageId = pageId} -> mkRenderSignal client processId ExitRenderProcess (ExitRenderProcessArgs pageId True) -- Clean up buffer list. return $ BufferList M.empty -- | Focus window. focusWindow :: Environment -> IO () focusWindow env = do focusCurrentTab env envGet env >>= highlightCurrentWindow -- | Highlight window. highlightCurrentWindow :: (Window, TVar FocusNotifierList) -> IO () highlightCurrentWindow (window, focusNotifierList) = focusNotifierShow (windowGetId window) focusNotifierList -- | Synchronization new tab in window. tabbarSyncNewTab :: Environment -> WindowId -> DaemonSignalArgs -> IO () tabbarSyncNewTab env wId (NewRenderPageConfirmArgs pageId _ _ _ processId modeName _ _ _) = do (tabbarTVar, (Tabbar tabbar, (windowList, (client, signalBoxList)))) <- envGet env -- Synchronization tab in all same mode window. forM_ (M.toList tabbar) $ \ ((windowId, pageModeName), tabSeq) -> -- When window mode same as tab mode and not current window. when (windowId /= wId && pageModeName == modeName) $ -- And tab haven't exist in current window. unless (any (\x -> tabProcessId x == processId) (F.toList tabSeq)) $ -- Then clone tab in current window. windowListGetWindow windowId windowList ?>= \window -> cloneTab window client tabbarTVar signalBoxList (envSignalBoxIdCounter env) (modeName, processId, pageId) -- | Get top-level container. getToplevelContainer :: Environment -> Container getToplevelContainer = toContainer . envFrame -- | Get focus window. getFocusWindow :: Environment -> IO Window getFocusWindow env = do let container = getToplevelContainer env focusNotifierList = envFocusNotifierList env windowList = envWindowList env windowNodeList = envWindowNodeList env focusWindow <- windowListGetFocusWindow windowList windowNodeList focusNotifierList container case focusWindow of Just window -> return window Nothing -> error "getFocusWindow: can't found any window." -- | Exit program. exit :: Environment -> Bool -> IO () exit env saveState = do -- Clean old status first. cleanOldLayoutState -- Save layout state. when saveState $ saveLayoutState env -- Send broadcast quit signal, other process can listen this signal when daemon process quit. client <- envGet env mkDaemonBroadcastSignal client ExitDaemonProcess ExitDaemonProcessArgs -- Need exit all render processes before exit daemon process. exitAllRenderProcess env -- Exit daemon process. mainQuit -- | Clean old yout state. cleanOldLayoutState :: IO () cleanOldLayoutState = do configDir <- getConfigDirectory let stateDir = configDir statePath isExist <- doesDirectoryExist stateDir when isExist $ do removeDirectoryRecursive stateDir createDirectoryIfMissing True stateDir -- | Save layout state. saveLayoutState :: Environment -> IO () saveLayoutState env = do -- Init. (BufferList bufferList, (Tabbar tabbar, (windowList, (ListZipper windowLeftList windowRightList, windowNodeList)))) <- envGet env -- Get window node state. let windowNodeStateCounter = listCounter windowNodeList windowNodeStateList <- fmap Set.fromList $ mapM (\ node -> WindowNodeState <$> pure (windowNodeId node) <*> readTVarIO (windowNodeParentId node) <*> readTVarIO (windowNodeChildLeftId node) <*> readTVarIO (windowNodeChildRightId node) <*> readTVarIO (windowNodeType node) <*> pure (windowNodeDirection node)) (setListGetList windowNodeList) -- Get window state. let getWindowStateList = mapM (\ Window {windowNode = node} -> do let winId = windowNodeId node (Rectangle _ _ w h) <- widgetGetAllocation $ windowNodePaned node return $ WindowState winId (w, h)) leftList <- getWindowStateList windowLeftList rightList <- getWindowStateList windowRightList -- Get tabbar state. pageIdCounter <- readTVarIO (envPageIdCounter env) signalBoxIdCounter <- readTVarIO (envSignalBoxIdCounter env) tabPages <- newTVarIO Set.empty tStateMap <- forM (M.toList tabbar) $ \ ((windowId, pageModeName), tabSeq) -> do tabbarStateSeq <- mapM (\tab -> do let pageId = tabPageId tab modifyTVarIO tabPages (Set.insert pageId) return $ TabState pageId (tabSignalBoxId tab)) $ F.toList tabSeq selectIndex <- case windowListGetWindow windowId windowList of Nothing -> do putStrLn $ "saveLayoutState(): Impossible that can't found window " ++ show windowId ++ " to save state." return 0 Just w -> notebookGetCurrentPage (windowNotebook w) return ((windowId, pageModeName), tabbarStateSeq, selectIndex) -- Get buffer list state. tPages <- readTVarIO tabPages let (foregroundPages, backgroundPages) = unzip $ map (\ (pageModeName, bufferSeq) -> let bufferList = map bufferPageId $ F.toList bufferSeq (fList, bList) = partition (`Set.member` tPages) bufferList in ((pageModeName, fList), (pageModeName, bList)) ) $ M.toList bufferList -- Save layout state. writeConfig layoutPath (Just (EnvironmentState (WindowNodeStateList windowNodeStateCounter windowNodeStateList) (WindowStateList leftList rightList) (TabbarState tStateMap pageIdCounter signalBoxIdCounter) (BufferListState foregroundPages backgroundPages) )) -- | Restore layout. restoreLayoutState :: Environment -> EnvironmentState -> IO () restoreLayoutState env EnvironmentState {envStateWindowNodeList = WindowNodeStateList {wnslCounter = nodeCounter ,wnslSet = nodeSet} ,envStateWindowList = WindowStateList {wslLeft = winLeftList ,wslRight = winRightList} ,envStateTabbar = TabbarState {tabbarStateList = tStates ,tabbarStatePageIdCounter = pageIdCounter ,tabbarStateSignalBoxIdCounter = signalBoxIdCounter} ,envStateBufferList = BufferListState {bufferListStateForegroundPages = fPages ,bufferListStateBackgroundPages = bPages} } = do -- Init. (tabbarTVar, (bufferListTVar, (signalBoxList, (focusNotifierListTVar, (windowNodeListTVar, windowListTVar))))) <- envGet env -- Restore window node. modifyTVarIO windowNodeListTVar (`setListSetCounter` nodeCounter) let nodeStateList = Set.toList nodeSet if null nodeStateList then putStrLn "Got empty window node list in restoreLayoutState(), it shouldn't happend." else do let rootNode = head nodeStateList restoreWindowNodeState WindowNodeState {windowNodeStateId = nId ,windowNodeStateParentId = pId ,windowNodeStateChildLeftId = clId ,windowNodeStateChildRightId = crId ,windowNodeStateType = nType ,windowNodeStateDirection = nDirection} = do -- Init. (windowNodeList, container) <- envGet env -- Get new window node. (newNode, newNodeList) <- windowNodeNewInternal (Just nId, pId, clId, crId, nType, nDirection) (windowNodeList, container) -- Show window node. widgetShowAll (windowNodePaned newNode) -- Update window node list. writeTVarIO windowNodeListTVar newNodeList -- Restore child node. let restoreChild childId = childId ?>= \ matchId -> maybeFindMin nodeSet (\x -> windowNodeStateId x == matchId) ?>= restoreWindowNodeState restoreChild clId restoreChild crId -- Restore all window node. restoreWindowNodeState rootNode -- Restore window state. windowNodeList <- readTVarIO windowNodeListTVar let getWindows winList = forM winList $ \ WindowState {windowStateId = wId ,windowStateSize = (width, height)} -> case windowNodeListGetNode windowNodeList wId of Just wNode -> do -- Restore window size. window <- windowNewWithNode wNode focusNotifierListTVar widgetSetSizeRequest (windowNodePaned $ windowNode window) width height return [window] Nothing -> do putStrLn $ "Create window " ++ show wId ++ " failed." return [] leftWindows <- fmap concat (getWindows winLeftList) rightWindows <- fmap concat (getWindows winRightList) -- Update window list. writeTVarIO windowListTVar (ListZipper leftWindows rightWindows) -- Restore tabbar state. writeTVarIO (envPageIdCounter env) pageIdCounter -- restore page id counter writeTVarIO (envSignalBoxIdCounter env) signalBoxIdCounter -- restore signal box id counter windowList <- readTVarIO windowListTVar forM_ tStates $ \ ((windowId, modeName), tabStateList, tabSelectIndex) -> case windowListGetWindow windowId windowList of Nothing -> putStrLn $ "restoreLayoutState(), can't restore tabs in window " ++ show windowId Just window -> do let notebook = windowNotebook window forM_ tabStateList $ \ TabState {tabStatePageId = pageId ,tabStateSignalBoxId = signalBoxId} -> do -- Init. uiFrame <- uiFrameStick notebook -- New signal box list. let signalBox = SignalBox signalBoxId uiFrame windowId modifyTVarIO signalBoxList (Set.insert signalBox) -- Update tabbar. modifyTVarIO tabbarTVar (tabbarAddTab windowId modeName (Tab 0 pageId signalBoxId 0 0 uiFrame)) -- Restore focus tab. notebookSetCurrentPage notebook tabSelectIndex -- Startup sub-process. let startupRenderProcess pages = forM_ pages $ \ (modeName, pageIds) -> forM_ pageIds $ \ pageId -> do -- Get startup path. configDir <- getConfigDirectory startupPath <- expandFilePath $ configDir statePath show pageId subprocessStartupPath isExist <- doesFileExist startupPath if isExist then do content <- readFile startupPath case (read content :: (String, SpawnProcessArgs)) of (binaryPath, startupArgs) -> do -- Update buffer list. modifyTVarIO bufferListTVar (bufferListAddBuffer (modeName, 0, pageId, "", "")) -- Startup sub process. runProcess_ binaryPath [show startupArgs] else putStrLn $ "Can't found startup file " ++ startupPath ++ " restore sub-process failed." startupRenderProcess fPages -- restore foreground sub-process startupRenderProcess bPages -- restore background sub-process -- Focus current window. focusWindow env return () instance ActionInputArgs Frame where envGet = return . envFrame instance ActionInputArgs Client where envGet = return . envDaemonClient instance ActionInputArgs Environment where envGet = return instance ActionInputArgs Window where envGet = getFocusWindow instance ActionInputArgs Container where envGet = return . getToplevelContainer instance ActionInputArgs WindowList where envGet = readTVarIO . envWindowList instance ActionInputArgs WindowNodeList where envGet = readTVarIO . envWindowNodeList instance ActionInputArgs FocusNotifierList where envGet = readTVarIO . envFocusNotifierList instance ActionInputArgs Tabbar where envGet = readTVarIO . envTabbar instance ActionInputArgs TabbarSelect where envGet = readTVarIO . envTabbarSelect instance ActionInputArgs BufferList where envGet = readTVarIO . envBufferList instance ActionInputArgs (TVar WindowList) where envGet = return . envWindowList instance ActionInputArgs (TVar WindowNodeList) where envGet = return . envWindowNodeList instance ActionInputArgs (TVar FocusNotifierList) where envGet = return . envFocusNotifierList instance ActionInputArgs (TVar SignalBoxList) where envGet = return . envSignalBoxList instance ActionInputArgs (TVar Tabbar) where envGet = return . envTabbar instance ActionInputArgs (TVar TabbarSelect) where envGet = return . envTabbarSelect instance ActionInputArgs (TVar BufferList) where envGet = return . envBufferList instance ActionInputArgs (TVar TabCloseHistory) where envGet = return . envTabCloseHistory instance ActionOutputArgs WindowList where envPut = writeTVarIO . envWindowList instance ActionOutputArgs WindowNodeList where envPut = writeTVarIO . envWindowNodeList instance ActionOutputArgs Tabbar where envPut = writeTVarIO . envTabbar instance ActionOutputArgs TabbarSelect where envPut = writeTVarIO . envTabbarSelect instance ActionOutputArgs BufferList where envPut = writeTVarIO . envBufferList