manatee-0.2.0: Manatee/Action/Basic.hs
-- Author: Andy Stewart <lazycat.manatee@gmail.com>
-- Maintainer: Andy Stewart <lazycat.manatee@gmail.com>
--
-- 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 <http://www.gnu.org/licenses/>.
{-# 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