{-# Language
FunctionalDependencies,
MultiParamTypeClasses,
DeriveDataTypeable,
ExistentialQuantification,
StandaloneDeriving,
FlexibleInstances,
ScopedTypeVariables,
FlexibleContexts,
CPP,
TypeFamilies #-}
-----------------------------------------------------------------------------
--
-- Module : Graphics.Frame
-- Copyright : Juergen Nicklisch-Franken
-- License : LGPL
--
-- Maintainer : maintainer@leksah.org
-- Stability : provisional
-- Portability : portabel
--
--
-- | Splittable panes containing notebooks with any widgets
--
---------------------------------------------------------------------------------
module Graphics.Frame (
-- * Pane class
Pane(..)
-- * Actions
, viewSwitchTabs
, viewTabsPos
, viewNewGroup
, viewCollapse
, viewSplitHorizontal
, viewSplitVertical
, viewMove
, viewDetach
, viewClosePane
, quit
-- * Events
, FrameEvent(..)
, FrameEventSel(..)
, triggerFrameEvent
, getFrameEvent
, registerFrameEvent
-- * Internals
, getMainWindow
, handleNotebookSwitch
, newNotebook
, viewSplit'
, paneDirectionToPosType
, viewDetach'
, viewNest'
, mbPaneFromName
, paneFromName
, posTypeToPaneDirection
, getNotebook
, getPaned
, viewCollapse'
, allGroupNames
, closeGroup
, GenPane(..)
, getPanes
-- * Accesing state
, initialFrameState
, registerFrameState
, getUiManagerSt
, getWindowsSt
, setWindowsSt
, getPanesSt
, setPanesSt
, getPaneMapSt
, setPaneMapSt
, getActivePaneSt
, setActivePaneSt
, getLayoutSt
, setLayoutSt
, setPaneTypes
, getPaneTypes
, getSessionExt
, setSessionExt
, getToolbar
, setToolbar
, getStatusbar
, setStatusbar
, getPanePathFromNB
, setPanePathFromNB
, getRecentPanes
, setRecentPanes
) where
import Base
import Graphics.Panes
import Graphics.FrameTypes
import Graphics.UI.Gtk hiding (afterToggleOverwrite,onToggleOverwrite)
import Control.Monad.Reader
import qualified Data.Map as Map
import Data.Map(Map)
import Data.List
import Data.Maybe
import Data.Unique
import Data.Typeable
import Data.Version
import System.Glib (GObjectClass(..), isA)
#if MIN_VERSION_gtk(0,10,5)
import Graphics.UI.Gtk.Layout.Notebook (gTypeNotebook)
#else
import Graphics.UI.Gtk.Types (gTypeNotebook)
#endif
import System.CPUTime (getCPUTime)
#if MIN_VERSION_gtk(0,10,5)
import Graphics.UI.Gtk.Gdk.EventM (Modifier(..))
#else
import Graphics.UI.Gtk.Gdk.Enums (Modifier(..))
#endif
import Graphics.UI.Gtk.Gdk.EventM (TimeStamp(..))
import qualified Data.Set as Set (unions, member)
import Data.Set (Set(..))
import Graphics.UI.Gtk.Gdk.Events (Event(..))
import Data.IORef(newIORef)
-- import Debug.Trace (trace)
trace a b = b
-- ----------------------------------
-- * Events
--
data FrameEventSel = FrameEventSel
deriving (Eq, Ord, Show, Typeable)
instance Selector FrameEventSel where
type ValueType FrameEventSel = EventChannel FrameEvent
--
-- | Events the gui frame triggers
--
data FrameEvent =
ActivatePane String
| DeactivatePane String
| MovePane String
| ChangeLayout
| RegisterActions [ActionDescr]
| RegisterPane [(String, GenPane)]
| RegisterSessionExt [GenSessionExtension]
| RegisterStatusbarComp [CompDescr]
| AboutToQuit Bool
makeFrameEvent :: StateM(EventChannel FrameEvent)
makeFrameEvent = makeEvent FrameEventSel
triggerFrameEvent :: FrameEvent -> StateM(FrameEvent)
triggerFrameEvent = triggerEvent FrameEventSel
getFrameEvent :: StateM (EventChannel FrameEvent)
getFrameEvent = getEvent FrameEventSel
registerFrameEvent hdl = getFrameEvent >>= \ev -> registerEvent ev hdl
-- ------------------------------------
-- * The state connected with frames
--
-- | Shows the state for the implementation of the GUI Frame
--
data FrameState = FrameState {
fsUiManager :: UIManager
, fsWindows :: [Window]
, fsPanes :: Map PaneName GenPane
, fsPaneMap :: (Map PaneName (PanePath, Connections)) -- these connections are from the build
, fsActivePane :: Maybe (PaneName, Connections) -- and these connections from activate
, fsPanePathFromNB :: Map Notebook PanePath
, fsLayout :: PaneLayout
, fsRecentPanes :: [PaneName]
, fsPaneTypes :: [(String,GenPane)] -- ^ The string is the paneType of the pane
-- the second arg encapsulates the real type
, fsSessionExt :: [GenSessionExtension]
, fsToolbar :: (Maybe Toolbar)
, fsStatusbar :: (Map CompName CompWidget, Maybe HBox)}
deriving Typeable
--
-- | Empty initial frame state
--
initialFrameState uim = FrameState {
fsUiManager = uim
, fsWindows = []
, fsPanes = Map.empty
, fsPaneMap = Map.empty
, fsActivePane = Nothing
, fsPanePathFromNB = Map.empty
, fsLayout = initialLayout
, fsRecentPanes = []
, fsPaneTypes = []
, fsSessionExt = []
, fsToolbar = (Nothing)
, fsStatusbar = (Map.empty,Nothing)}
data GenPane = forall alpha beta . Pane alpha => PaneC alpha
instance Eq GenPane where
(==) (PaneC x) (PaneC y) = paneName x == paneName y
instance Ord GenPane where
(<=) (PaneC x) (PaneC y) = paneName x <= paneName y
instance Show GenPane where
show (PaneC x) = "Pane " ++ paneName x
-- ---------------------------------------------------------------------
-- * Accessor functions
--
getThis :: (FrameState -> alpha) -> StateM alpha
getThis sel = do
st <- getFrameState
return (sel st)
setThis :: (FrameState -> alpha -> FrameState) -> alpha -> StateM ()
setThis sel value = do
st <- getFrameState
setFrameState (sel st value)
getWindowsSt = getThis fsWindows
setWindowsSt = setThis (\st value -> st{fsWindows = value})
getUiManagerSt = getThis fsUiManager
getPanesSt = getThis fsPanes
setPanesSt = setThis (\st value -> st{fsPanes = value})
getPaneMapSt = getThis fsPaneMap
setPaneMapSt = setThis (\st value -> st{fsPaneMap = value})
getActivePaneSt = getThis fsActivePane
setActivePaneSt = setThis (\st value -> st{fsActivePane = value})
getLayoutSt = getThis fsLayout
setLayoutSt = setThis (\st value -> st{fsLayout = value})
getPanePathFromNB = getThis fsPanePathFromNB
setPanePathFromNB = setThis (\st value -> st{fsPanePathFromNB = value})
getRecentPanes = getThis fsRecentPanes
setRecentPanes = setThis (\st value -> st{fsRecentPanes = value})
getPaneTypes = getThis fsPaneTypes
setPaneTypes = setThis (\st value -> st{fsPaneTypes = value})
getSessionExt = getThis fsSessionExt
setSessionExt = setThis (\st value -> st{fsSessionExt = value})
getToolbar = getThis fsToolbar
setToolbar = setThis (\st value -> st{fsToolbar = value})
getStatusbar = getThis fsStatusbar
setStatusbar = setThis (\st value -> st{fsStatusbar = value})
--
-- | The handling of the state of the frame
--
data FrameStateSel = FrameStateSel
deriving (Eq, Ord, Show, Typeable)
instance Selector FrameStateSel where
type ValueType FrameStateSel = FrameState
registerFrameState :: FrameState -> StateM (Maybe String)
registerFrameState = registerState FrameStateSel
setFrameState :: FrameState -> StateM ()
setFrameState = setState FrameStateSel
getFrameState :: StateM (FrameState)
getFrameState = getState FrameStateSel
-- | Quit ide -- TODO
quit :: StateAction
quit = do
AboutToQuit shallQuit <- triggerFrameEvent (AboutToQuit True)
when shallQuit (liftIO mainQuit)
-- ----------------------------------------
-- * The main interface to the frame system
--
-- | All kinds of panes are instances of Pane
--
class PaneInterface alpha => Pane alpha where
paneName :: alpha -> PaneName
-- ^ gets a string which names this pane, which may include an added index ...
paneName b = if getAddedIndex b == 0
then primPaneName b
else primPaneName b ++ "(" ++ show (getAddedIndex b) ++ ")"
getAddedIndex :: alpha -> Int
-- ^ ..., which is used if more then one pane has the same name
getAddedIndex _ = 0
makeActive :: alpha -> Connections -> StateM ()
-- ^ activates this pane, should probably be private
makeActive pane conn = do
mbAP <- getActivePaneSt
case mbAP of
Just (pn,_) | pn == paneName pane -> return ()
_ -> do
deactivatePane
liftIO $ bringPaneToFront pane
setActivePaneSt (Just (paneName pane,conn))
-- use it for error reporting
triggerFrameEvent (ActivatePane (paneName pane))
recent <- getRecentPanes
setRecentPanes
(paneName pane : filter (/= paneName pane) recent)
return ()
closePane :: alpha -> StateM Bool
-- ^ closes this pane
closePane pane = do
(panePath,conn) <- guiPropertiesFromName (paneName pane)
nb <- getNotebook panePath
mbI <- liftIO $notebookPageNum nb (getTopWidget pane)
case mbI of
Nothing -> liftIO $ do
error ("notebook page not found: unexpected " ++ paneName pane ++ " " ++ show panePath)
return False
Just i -> do
liftIO $ signalDisconnectAll conn
liftIO $ do
notebookRemovePage nb i
widgetDestroy (getTopWidget pane)
deactivatePaneIfActive pane
removePaneAdmin pane
recent <- getRecentPanes
setRecentPanes (filter (/= paneName pane) recent)
return True
getPane :: StateM (Maybe alpha)
-- ^get a pane of this type, if one is open
getPane = do
selectedPanes <- getPanes
if null selectedPanes || length selectedPanes > 1
then return Nothing
else (return (Just $ head selectedPanes))
forceGetPane :: Either PanePath String -> PaneArgs alpha -> StateM alpha
-- ^get a pane of this type, if not one is open panic
forceGetPane pp arg = do mbPane <- getOrBuildPane pp arg
case mbPane of
Nothing -> error "Can't get pane "
Just p -> return p
getOrBuildPane :: Either PanePath String -> PaneArgs alpha -> StateM (Maybe alpha)
-- ^get a pane of this type, if one is open, or build one and for this specify either
-- a pane path to put it, or a group name, from which a pane path may be derived
getOrBuildPane ePpoPid arg = do
mbPane <- getPane
case mbPane of
Nothing -> do
pp <- case ePpoPid of
Right pId -> getBestPathForId pId
Left ppp -> do
layout <- getLayoutSt
return (getBestPanePath ppp layout)
nb <- getNotebook pp
buildPanePrim pp nb (builder arg)
Just pane -> return (Just pane)
buildPane :: Either PanePath String -> PaneArgs alpha -> StateM (Maybe alpha)
-- ^build a pane of this specific type
buildPane ePpoPid arg = do
pp <- case ePpoPid of
Right pId -> getBestPathForId pId
Left ppp -> do
layout <- getLayoutSt
return (getBestPanePath ppp layout)
nb <- getNotebook pp
buildPanePrim pp nb (builder arg)
displayPane :: alpha -> Bool -> StateM ()
-- ^ makes this pane visible
displayPane pane shallGrabFocus = do
liftIO $ bringPaneToFront pane
when shallGrabFocus $ liftIO $ widgetGrabFocus $ getTopWidget pane
getOrBuildDisplay :: Either PanePath String -> Bool -> PaneArgs alpha -> StateM (Maybe alpha)
-- ^ is a concatination of getOrBuildPane and displayPane
getOrBuildDisplay pps b arg = do
mbP <- getOrBuildPane pps arg
case mbP of
Nothing -> return Nothing
Just p -> do
displayPane p b
return (Just p)
buildPanePrim :: PanePath ->
Notebook ->
(PanePath -> Notebook -> Window -> StateM (Maybe alpha,Connections)) ->
StateM (Maybe alpha)
buildPanePrim panePath notebook builder = do
windows <- getWindowsSt
(mbBuf,cids) <- builder panePath notebook (head windows)
case mbBuf of
Nothing -> return Nothing
Just buf -> do
panes' <- getPanesSt
paneMap' <- getPaneMapSt
let b1 = case Map.lookup (paneName buf) paneMap' of
Nothing -> True
Just it -> False
let b2 = case Map.lookup (paneName buf) panes' of
Nothing -> True
Just it -> False
if b1 && b2
then do
idx <- notebookInsertOrdered notebook (getTopWidget buf) (paneName buf) Nothing False
mbPage <- liftIO $ notebookGetNthPage notebook idx
mbCid <- case mbPage of
Nothing -> return Nothing
Just page -> liftM Just (reifyState (\ stateR ->
on (castToContainer page) setFocusChild (\ _ ->
liftIO (reflectState (makeActive buf []) stateR))))
addPaneAdmin buf (case mbCid of {Nothing -> cids; Just c -> castCID c:cids}) panePath
liftIO $ do
widgetSetName (getTopWidget buf) (paneName buf)
widgetShowAll (getTopWidget buf)
widgetGrabFocus (getTopWidget buf)
bringPaneToFront buf
return (Just buf)
else return Nothing
setChanged :: alpha -> Bool -> StateM ()
-- ^ Set the state of this pane to changed or not changed
setChanged pane hasChanged = liftIO $ do
let topWidget = getTopWidget pane
mbNb <- getNotebookForWidget topWidget
case mbNb of
Nothing -> return ()
Just nb -> markLabel nb topWidget hasChanged
-- ---------------------------------------------------------------------
-- Activating and deactivating Panes.
-- This is here and not in Views because it needs some dependencies
-- (e.g. Events for history)
--
deactivatePane :: StateAction
deactivatePane = do
mbAP <- getActivePaneSt
case mbAP of
Nothing -> return ()
Just (name,signals) -> do
liftIO $ signalDisconnectAll signals
setActivePaneSt Nothing
triggerFrameEvent (DeactivatePane name)
return ()
deactivatePaneWithoutEvents :: StateAction
deactivatePaneWithoutEvents = do
mbAP <- getActivePaneSt
case mbAP of
Just (_,signals) -> liftIO $do
signalDisconnectAll signals
Nothing -> return ()
setActivePaneSt Nothing
deactivatePaneIfActive :: Pane alpha => alpha -> StateAction
deactivatePaneIfActive pane = do
mbActive <- getActivePaneSt
case mbActive of
Nothing -> return ()
Just (n,_) -> if n == paneName pane
then deactivatePane
else return ()
addPaneAdmin :: Pane alpha => alpha -> Connections -> PanePath -> StateM Bool
addPaneAdmin pane conn pp = do
panes' <- getPanesSt
paneMap' <- getPaneMapSt
liftIO $ widgetSetName (getTopWidget pane) (paneName pane)
let b1 = case Map.lookup (paneName pane) paneMap' of
Nothing -> True
Just it -> False
let b2 = case Map.lookup (paneName pane) panes' of
Nothing -> True
Just it -> False
if b1 && b2
then do
setPaneMapSt (Map.insert (paneName pane) (pp, conn) paneMap')
setPanesSt (Map.insert (paneName pane) (PaneC pane) panes')
return True
else do
return False
paneFromName :: PaneName -> StateM GenPane
paneFromName pn = do
mbPane <- mbPaneFromName pn
case mbPane of
Just p -> return p
Nothing -> error $ "ViewFrame>>paneFromName:Can't find pane from unique name " ++ pn
mbPaneFromName :: PaneName -> StateM (Maybe GenPane)
mbPaneFromName pn = do
panes <- getPanesSt
return (Map.lookup pn panes)
type StandardPath = PanePath
--
-- | Get a valid panePath from a standard path.
--
getBestPanePath :: StandardPath -> PaneLayout -> PanePath
getBestPanePath sp pl = reverse $ getStandard' sp pl []
where
getStandard' (GroupP group:sp) (TerminalP {paneGroups = groups}) p
| group `Map.member` groups = getStandard' sp (groups Map.! group) (GroupP group:p)
getStandard' _ (TerminalP {}) p = p
getStandard' (SplitP LeftP:sp) (VerticalP l r _) p = getStandard' sp l (SplitP LeftP:p)
getStandard' (SplitP RightP:sp) (VerticalP l r _) p = getStandard' sp r (SplitP RightP:p)
getStandard' (SplitP TopP:sp) (HorizontalP t b _) p = getStandard' sp t (SplitP TopP:p)
getStandard' (SplitP BottomP:sp) (HorizontalP t b _) p = getStandard' sp b (SplitP BottomP:p)
-- if no match get leftmost topmost
getStandard' _ (VerticalP l r _) p = getStandard' [] l (SplitP LeftP:p)
getStandard' _ (HorizontalP t b _) p = getStandard' [] t (SplitP TopP:p)
--
-- | Get a standard path.
--
getBestPathForId :: String -> StateM PanePath
getBestPathForId id = do
p <- panePathForGroup id
l <- getLayoutSt
return (getBestPanePath p l)
findGroupPath :: String -> PaneLayout -> Maybe PanePath
findGroupPath group layout =
let terminalPairs = terminalsWithPanePath layout
in case (filter filterFunc terminalPairs) of
[] -> Nothing
(pp,_) : [] -> Just (pp ++ [GroupP group])
_ -> error ("ViewFrame>>group name not unique: " ++ group)
where
filterFunc (_,(TerminalP groups _ _ _ _)) = group `Set.member` Map.keysSet groups
filterFunc _ = error "ViewFrame>>findGroupPath: impossible"
terminalsWithPanePath :: PaneLayout -> [(PanePath,PaneLayout)]
terminalsWithPanePath pl = map (\ (pp,l) -> (reverse pp,l)) $ terminalsWithPP [] pl
where
terminalsWithPP pp t@(TerminalP groups _ _ _ _) = [(pp,t)]
++ concatMap (terminalsFromGroup pp) (Map.toList groups)
terminalsWithPP pp (VerticalP l r _) = terminalsWithPP (SplitP LeftP : pp) l
++ terminalsWithPP (SplitP RightP : pp) r
terminalsWithPP pp (HorizontalP t b _) = terminalsWithPP (SplitP TopP : pp) t
++ terminalsWithPP (SplitP BottomP : pp) b
terminalsFromGroup pp (name,layout) = terminalsWithPP (GroupP name : pp) layout
getNotebookOrPaned :: PanePath -> (Widget -> beta) -> StateM beta
getNotebookOrPaned p cf = do
layout <- getLayoutSt
mwn <- mainWindowName
(widgetGet $ getWidgetNameList p layout mwn) cf
widgetGet :: [String] -> (Widget -> b) -> StateM (b)
widgetGet strL cf = do
windows <- getWindowsSt
r <- liftIO $chooseWidgetFromPath (map castToWidget windows) strL
return (cf r)
getWidgetNameList :: PanePath -> PaneLayout -> String -> [String]
getWidgetNameList path layout mainWindowName = reverse $ nameList (reverse path) (reverse $ layoutsFromPath path layout)
where
nameList [] _ = reverse [mainWindowName,"topBox","root"]
nameList (pe:_) (TerminalP{detachedId = Just id}:_) = [panePathElementToWidgetName pe, id]
nameList (pe:rpath) (_:rlayout) = panePathElementToWidgetName pe : nameList rpath rlayout
nameList _ _ = error $ "inconsistent layout (getWidgetNameList) " ++ show path ++ " " ++ show layout
mainWindowName :: StateM String
mainWindowName = do
windows <- getWindowsSt
case windows of
[] -> return ""
(w:_) -> liftIO (widgetGetName w)
--
-- | Bring the pane to the front position in its notebook
--
bringPaneToFront :: Pane alpha => alpha -> IO ()
bringPaneToFront pane = do
let tv = getTopWidget pane
setCurrentNotebookPages tv
setCurrentNotebookPages widget = do
mbParent <- widgetGetParent widget
case mbParent of
Just parent -> do
setCurrentNotebookPages parent
if parent `isA` gTypeNotebook
then do
mbPageNum <- notebookPageNum ((castToNotebook' "setCurrentNotebookPage 1") parent) widget
case mbPageNum of
Just pageNum -> do
notebookSetCurrentPage ((castToNotebook' "setCurrentNotebookPage 2") parent) pageNum
return ()
Nothing -> return ()
else return ()
Nothing -> return ()
getNotebookForWidget :: (WidgetClass alpha) => alpha -> IO (Maybe Notebook)
getNotebookForWidget widget = do
mbParent <- widgetGetParent widget
case mbParent of
Just parent -> do
if parent `isA` gTypeNotebook
then return (Just (castToNotebook parent))
else return Nothing
Nothing -> return Nothing
-- | Translates a pane direction to the widget name
--
paneDirectionToWidgetName :: PaneDirection -> String
paneDirectionToWidgetName TopP = "top"
paneDirectionToWidgetName BottomP = "bottom"
paneDirectionToWidgetName LeftP = "left"
paneDirectionToWidgetName RightP = "right"
panePathElementToWidgetName :: PanePathElement -> String
panePathElementToWidgetName (SplitP dir) = paneDirectionToWidgetName dir
panePathElementToWidgetName (GroupP group) = groupPrefix ++ group
--
-- | Get the layout at the given pane path
--
layoutFromPath :: PanePath -> PaneLayout -> PaneLayout
layoutFromPath [] l = l
layoutFromPath (GroupP group:r) (TerminalP {paneGroups = groups})
| group `Map.member` groups = layoutFromPath r (groups Map.! group)
layoutFromPath (SplitP TopP:r) (HorizontalP t _ _) = layoutFromPath r t
layoutFromPath (SplitP BottomP:r) (HorizontalP _ b _) = layoutFromPath r b
layoutFromPath (SplitP LeftP:r) (VerticalP l _ _) = layoutFromPath r l
layoutFromPath (SplitP RightP:r) (VerticalP _ ri _) = layoutFromPath r ri
layoutFromPath pp l = error
$"inconsistent layout (layoutFromPath) " ++ show pp ++ " " ++ show l
layoutsFromPath :: PanePath -> PaneLayout -> [PaneLayout]
layoutsFromPath (GroupP group:r) layout@(TerminalP {paneGroups = groups})
| group `Map.member` groups
= layout:layoutsFromPath r (groups Map.! group)
layoutsFromPath [] layout = [layout]
layoutsFromPath (SplitP TopP:r) layout@(HorizontalP t b _) = layout:layoutsFromPath r t
layoutsFromPath (SplitP BottomP:r) layout@(HorizontalP t b _) = layout:layoutsFromPath r b
layoutsFromPath (SplitP LeftP:r) layout@(VerticalP l ri _) = layout:layoutsFromPath r l
layoutsFromPath (SplitP RightP:r) layout@(VerticalP l ri _) = layout:layoutsFromPath r ri
layoutsFromPath pp l = error
$"inconsistent layout (layoutsFromPath) " ++ show pp ++ " " ++ show l
--
-- | Get the widget from a list of strings
--
widgetFromPath :: Widget -> [String] -> IO (Widget)
widgetFromPath w [] = return w
widgetFromPath w path = do
children <- containerGetChildren (castToContainer w)
chooseWidgetFromPath children path
chooseWidgetFromPath :: [Widget] -> [String] -> IO (Widget)
chooseWidgetFromPath _ [] = error $"Cant't find widget (empty path)"
chooseWidgetFromPath widgets (h:t) = do
names <- mapM widgetGetName widgets
let mbiInd = findIndex (== h) names
case mbiInd of
Nothing -> error $"Cant't find widget path " ++ show (h:t) ++ " found only " ++ show names
Just ind -> widgetFromPath (widgets !! ind) t
--
-- | Get the notebook widget for the given pane path
--
getNotebook :: PanePath -> StateM Notebook
getNotebook p = getNotebookOrPaned p (castToNotebook' ("getNotebook " ++ show p))
getNotebook' :: String -> PanePath -> StateM Notebook
getNotebook' str p = getNotebookOrPaned p (castToNotebook' ("getNotebook' " ++ str ++ " " ++ show p))
castToNotebook' :: GObjectClass obj => String -> obj -> Notebook
castToNotebook' str obj = if obj `isA` gTypeNotebook
then castToNotebook obj
else error ("Not a notebook " ++ str)
notebookInsertOrdered :: (NotebookClass self, WidgetClass child)
=> self
-> child -- child - the Widget to use as the contents of the page.
-> String
-> Maybe Label -- the label for the page as String or Label
-> Bool
-> StateM Int
notebookInsertOrdered nb widget labelStr mbLabel isGroup = do
label <- case mbLabel of
Nothing -> liftIO $ labelNew (Just labelStr)
Just l -> return l
menuLabel <- liftIO $ labelNew (Just labelStr)
numPages <- liftIO $ notebookGetNPages nb
mbWidgets <- liftIO $ mapM (notebookGetNthPage nb) [0 .. (numPages-1)]
let widgets = map (\v -> forceJust v "ViewFrame.notebookInsertOrdered: no widget") mbWidgets
labelStrs <- liftIO $ mapM widgetGetName widgets
let pos = case findIndex (\ s -> withoutGroupPrefix s > withoutGroupPrefix labelStr) labelStrs of
Just i -> i
Nothing -> -1
labelBox <- if isGroup then groupLabel labelStr else mkLabelBox label labelStr
liftIO $ do
markLabel nb labelBox False
realPos <- notebookInsertPageMenu nb widget labelBox menuLabel pos
widgetShowAll labelBox
notebookSetCurrentPage nb realPos
return realPos
--
-- | used to identify a group from a pane name
--
groupPrefix = "_group_"
--
-- | Get a pane name without group prefix
--
withoutGroupPrefix :: String -> String
withoutGroupPrefix s = case groupPrefix `stripPrefix` s of
Nothing -> s
Just s' -> s'
groupLabel :: String -> StateM EventBox
groupLabel group = do
label <- liftIO $ labelNew Nothing
liftIO $ labelSetUseMarkup label True
liftIO $ labelSetMarkup label ("" ++ group ++ "")
labelBox <- mkLabelBox label (groupPrefix ++ group)
liftIO $ widgetShowAll labelBox
return labelBox
-- | Add the change mark or removes it
markLabel :: (WidgetClass alpha, NotebookClass beta) => beta -> alpha -> Bool -> IO ()
markLabel nb topWidget modified = do
mbBox <- notebookGetTabLabel nb topWidget
case mbBox of
Nothing -> return ()
Just box -> do
mbContainer <- binGetChild (castToBin box)
case mbContainer of
Nothing -> return ()
Just container -> do
children <- containerGetChildren (castToContainer container)
let label = castToLabel $ forceHead children "ViewFrame>>markLabel: empty children"
text <- widgetGetName topWidget
labelSetUseMarkup (castToLabel label) True
labelSetMarkup (castToLabel label)
(if modified
then "" ++ text ++ ""
else text)
-- | Returns a label box
mkLabelBox :: Label -> String -> StateM EventBox
mkLabelBox lbl paneName = do
(tb,lb) <- liftIO $ do
miscSetAlignment (castToMisc lbl) 0.0 0.0
miscSetPadding (castToMisc lbl) 0 0
labelBox <- eventBoxNew
eventBoxSetVisibleWindow labelBox False
innerBox <- hBoxNew False 0
tabButton <- buttonNew
widgetSetName tabButton "leksah-close-button"
buttonSetFocusOnClick tabButton False
buttonSetRelief tabButton ReliefNone
buttonSetAlignment tabButton (0.0,0.0)
image <- imageNewFromStock stockClose IconSizeMenu
mbPB <- widgetRenderIcon tabButton stockClose IconSizeMenu ""
(height,width) <- case mbPB of
Nothing -> return (14,14)
Just pb -> do
h <- pixbufGetHeight pb
w <- pixbufGetWidth pb
return (h,w)
on tabButton styleSet (\style -> do
widgetSetSizeRequest tabButton (height + 2) (width + 2))
containerSetBorderWidth tabButton 0
containerAdd tabButton image
boxPackStart innerBox lbl PackNatural 0
boxPackEnd innerBox tabButton PackNatural 0
containerAdd labelBox innerBox
dragSourceSet labelBox [Button1] [ActionCopy,ActionMove]
tl <- targetListNew
targetListAddTextTargets tl 0
dragSourceSetTargetList labelBox tl
on labelBox dragDataGet (\ cont id timeStamp -> do
selectionDataSetText paneName
return ())
return (tabButton,labelBox)
cl <- runInIO closeHandler
liftIO $ onClicked tb (cl ())
return lb
where
closeHandler :: () -> StateM ()
closeHandler _ = case groupPrefix `stripPrefix` paneName of
Just group -> do
closeGroup group
Nothing -> do
(PaneC pane) <- paneFromName paneName
closePane pane
return ()
closeGroup :: String -> StateM ()
closeGroup groupName = do
layout <- getLayoutSt
let mbPath = findGroupPath groupName layout
mainWindow <- getMainWindow
case mbPath of
Nothing -> message Warning ("ViewFrame>>closeGroup: Group path not found: " ++ groupName) >> return ()
Just path -> do
panesMap <- getPaneMapSt
let nameAndpathList = filter (\(a,pp) -> path `isPrefixOf` pp)
$ map (\(a,b) -> (a,fst b)) (Map.assocs panesMap)
continue <- case nameAndpathList of
(_:_) -> liftIO $ do
md <- messageDialogNew (Just mainWindow) [] MessageQuestion ButtonsYesNo
("Group " ++ groupName ++ " not empty. Close with all contents?")
rid <- dialogRun md
widgetDestroy md
case rid of
ResponseYes -> return True
otherwise -> return False
[] -> return True
when continue $ do
panes <- mapM paneFromName $ map fst nameAndpathList
results <- mapM (\ (PaneC p) -> closePane p) panes
when (foldr (&&) True results) $ do
nbOrPaned <- getNotebookOrPaned path castToWidget
mbParent <- liftIO $ widgetGetParent nbOrPaned
case mbParent of
Nothing -> error "ViewFrame>>closeGroup: closeGroup: no parent"
Just parent -> liftIO $ containerRemove (castToContainer parent) nbOrPaned
setLayoutSt (removeGL path layout)
ppMap <- getPanePathFromNB
setPanePathFromNB (Map.filter (\pa -> not (path `isPrefixOf` pa)) ppMap)
getMainWindow = liftM head getWindowsSt
--
-- | Remove group layout at a certain path
--
removeGL :: PanePath -> PaneLayout -> PaneLayout
removeGL [GroupP group] t@(TerminalP oldGroups _ _ _ _)
| group `Map.member` oldGroups = t{paneGroups = group `Map.delete` oldGroups}
removeGL (GroupP group:r) old@(TerminalP {paneGroups = groups})
| group `Map.member` groups = old{paneGroups = Map.adjust (removeGL r) group groups}
removeGL (SplitP TopP:r) (HorizontalP tp bp _) = HorizontalP (removeGL r tp) bp 0
removeGL (SplitP BottomP:r) (HorizontalP tp bp _) = HorizontalP tp (removeGL r bp) 0
removeGL (SplitP LeftP:r) (VerticalP lp rp _) = VerticalP (removeGL r lp) rp 0
removeGL (SplitP RightP:r) (VerticalP lp rp _) = VerticalP lp (removeGL r rp) 0
removeGL p l = error $"ViewFrame>>removeGL: inconsistent layout " ++ show p ++ " " ++ show l
removePaneAdmin :: Pane alpha => alpha -> StateM ()
removePaneAdmin pane = do
panes' <- getPanesSt
paneMap' <- getPaneMapSt
setPanesSt (Map.delete (paneName pane) panes')
setPaneMapSt (Map.delete (paneName pane) paneMap')
getPanePrim :: Typeable alpha => Pane alpha => StateM (Maybe alpha)
getPanePrim = do
selectedPanes <- getPanes
if null selectedPanes || length selectedPanes > 1
then return Nothing
else (return (Just $ head selectedPanes))
--
-- | Get all panes of a certain type
--
getPanes :: Typeable alpha => Pane alpha => StateM ([alpha])
getPanes = do
panes' <- getPanesSt
return (catMaybes
$ map (\(PaneC p) -> cast p)
$ Map.elems panes')
-- | Constructs a unique pane name, which is an index and a string
figureOutPaneName :: String -> Int -> StateM (Int,String)
figureOutPaneName bn ind = do
bufs <- getPanesSt
let ind = foldr (\(PaneC buf) ind ->
if primPaneName buf == bn
then max ind ((getAddedIndex buf) + 1)
else ind)
0 (Map.elems bufs)
if ind == 0
then return (0,bn)
else return (ind,bn ++ "(" ++ show ind ++ ")")
-- |
guiPropertiesFromName :: PaneName -> StateM (PanePath, Connections)
guiPropertiesFromName pn = do
paneMap <- getPaneMapSt
case Map.lookup pn paneMap of
Just it -> return it
otherwise -> error $"Cant't find guiProperties from unique name " ++ pn
posTypeToPaneDirection PosLeft = LeftP
posTypeToPaneDirection PosRight = RightP
posTypeToPaneDirection PosTop = TopP
posTypeToPaneDirection PosBottom = BottomP
paneDirectionToPosType LeftP = PosLeft
paneDirectionToPosType RightP = PosRight
paneDirectionToPosType TopP = PosTop
paneDirectionToPosType BottomP = PosBottom
--
-- | Closes the current pane
--
viewClosePane :: StateM ()
viewClosePane = do
mbPane <- getActivePaneSt
case mbPane of
Nothing -> do
return ()
Just (paneName,_) -> do
(PaneC pane) <- paneFromName paneName
closePane pane >> return ()
--
-- | Toggle the tabs of the current notebook
--
viewSwitchTabs :: StateM ()
viewSwitchTabs = do
mbNb <- getActiveNotebook
case mbNb of
Nothing -> return ()
Just nb -> liftIO $ do
b <- notebookGetShowTabs nb
notebookSetShowTabs nb (not b)
--
-- | Sets the tab position in the current notebook
--
viewTabsPos :: PositionType -> StateM ()
viewTabsPos pos = do
mbNb <- getActiveNotebook
case mbNb of
Nothing -> return ()
Just nb -> liftIO $notebookSetTabPos nb pos
--
-- | Split the currently active pane in horizontal direction
--
viewSplitHorizontal :: StateM ()
viewSplitHorizontal = viewSplit Horizontal
--
-- | Split the currently active pane in vertical direction
--
viewSplitVertical :: StateM ()
viewSplitVertical = viewSplit Vertical
--
-- | The active view can be split in two (horizontal or vertical)
--
viewSplit :: Direction -> StateM ()
viewSplit dir = do
mbPanePath <- getActivePanePath
case mbPanePath of
Nothing -> return ()
Just panePath -> do
viewSplit' panePath dir
viewSplit' :: PanePath -> Direction -> StateM ()
viewSplit' panePath dir = do
l <- getLayoutSt
case layoutFromPath panePath l of
(TerminalP _ _ _ (Just _) _) -> message Warning ("ViewFrame>>viewSplit': can't split detached: ") >> return ()
_ -> do
activeNotebook <- (getNotebook' "viewSplit") panePath
ind <- liftIO $ notebookGetCurrentPage activeNotebook
mbPD <- do
mbParent <- liftIO $ widgetGetParent activeNotebook
case mbParent of
Nothing -> message Warning ("ViewFrame>>viewSplit': parent not found: ") >> return Nothing
Just parent -> do
(nb,paneDir) <- do
let (name,altname,paneDir,
oldPath,newPath) = case dir of
Horizontal -> ("top",
"bottom",
TopP,
panePath ++ [SplitP TopP],
panePath ++ [SplitP BottomP])
Vertical -> ("left",
"right",
LeftP,
panePath ++ [SplitP LeftP],
panePath ++ [SplitP RightP])
adjustNotebooks panePath oldPath
ppNb <- getPanePathFromNB
setPanePathFromNB $ Map.insert activeNotebook oldPath ppNb
nb <- newNotebook newPath
(np,nbi) <- liftIO $ do
newpane <- case dir of
Horizontal -> do h <- vPanedNew
return (castToPaned h)
Vertical -> do v <- hPanedNew
return (castToPaned v)
rName <- widgetGetName activeNotebook
widgetSetName newpane rName
widgetSetName nb altname
panedPack2 newpane nb True True
nbIndex <- if parent `isA` gTypeNotebook
then notebookPageNum ((castToNotebook' "viewSplit'1") parent) activeNotebook
else trace ("ViewFrame>>viewSplit': parent not a notebook: ")
$ return Nothing
containerRemove (castToContainer parent) activeNotebook
widgetSetName activeNotebook name
panedPack1 newpane activeNotebook True True
return (newpane,nbIndex)
case (reverse panePath, nbi) of
(SplitP dir:_, _)
| dir `elem` [TopP, LeftP] -> liftIO $ panedPack1 (castToPaned parent) np True True
| otherwise -> liftIO $ panedPack2 (castToPaned parent) np True True
(GroupP group:_, Just n) -> do
liftIO $ notebookInsertPage ((castToNotebook' "viewSplit' 2") parent) np group n
label <- groupLabel group
liftIO $ notebookSetTabLabel ((castToNotebook' "viewSplit' 3") parent) np label
label2 <- groupMenuLabel group
liftIO $ notebookSetMenuLabel ((castToNotebook' "viewSplit' 4") parent) np label2
return ()
([], _) -> do
liftIO $ boxPackStart (castToBox parent) np PackGrow 0
liftIO $ boxReorderChild (castToVBox parent) np 2
_ -> error "No notebook index found in viewSplit"
liftIO $ do
widgetShowAll np
widgetGrabFocus activeNotebook
case nbi of
Just n -> do
notebookSetCurrentPage ((castToNotebook' "viewSplit' 5") parent) n
return ()
_ -> trace ("ViewFrame>>viewSplit': parent not a notebook2: ")
$ return ()
return (nb,paneDir)
handleFunc <- runInIO (handleNotebookSwitch nb)
liftIO $ afterSwitchPage nb handleFunc
return (Just (paneDir,dir))
case mbPD of
Just (paneDir,pdir) -> do
adjustPanes panePath (panePath ++ [SplitP paneDir])
adjustLayoutForSplit paneDir panePath
mbWidget <- liftIO $ notebookGetNthPage activeNotebook ind
when (isJust mbWidget) $ do
name <- liftIO $ widgetGetName (fromJust mbWidget)
mbPane <- mbPaneFromName name
case mbPane of
Just (PaneC pane) -> move (panePath ++ [SplitP (otherDirection paneDir)]) pane
Nothing -> return ()
Nothing -> return ()
--
-- | Two notebooks can be collapsed to one
--
viewCollapse :: StateM ()
viewCollapse = do
mbPanePath <- getActivePanePath
case mbPanePath of
Nothing -> return ()
Just panePath -> do
viewCollapse' panePath
viewCollapse' :: PanePath -> StateM ()
viewCollapse' panePath = do
layout1 <- getLayoutSt
case layoutFromPath panePath layout1 of
(TerminalP _ _ _ (Just _) _) -> message Debug ("ViewFrame>>viewCollapse': can't collapse detached: ")
>> return ()
_ -> do
let newPanePath = init panePath
let mbOtherSidePath = otherSide panePath
case mbOtherSidePath of
Nothing -> trace ("ViewFrame>>viewCollapse': no other side path found: ") return ()
Just otherSidePath -> do
nbop <- getNotebookOrPaned otherSidePath castToWidget
let nb = if nbop `isA` gTypeNotebook
then Just ((castToNotebook' "viewCollapse' 0") nbop)
else Nothing
case nb of
Nothing -> trace ("ViewFrame>>viewCollapse': other side path not collapsedXX: ") $
case layoutFromPath otherSidePath layout1 of
VerticalP _ _ _ -> do
viewCollapse' (otherSidePath ++ [SplitP LeftP])
viewCollapse' panePath
HorizontalP _ _ _ -> do
viewCollapse' (otherSidePath ++ [SplitP TopP])
viewCollapse' panePath
otherwise -> trace ("ViewFrame>>viewCollapse': impossible1 ") return ()
Just otherSideNotebook -> do
paneMap <- getPaneMapSt
activeNotebook <- (getNotebook' "viewCollapse' 1") panePath
-- 1. Move panes and groups to one side (includes changes to paneMap and layout)
let paneNamesToMove = map (\(w,(p,_)) -> w)
$filter (\(w,(p,_)) -> otherSidePath == p)
$Map.toList paneMap
panesToMove <- mapM paneFromName paneNamesToMove
mapM_ (\(PaneC p) -> move panePath p) panesToMove
let groupNames = map (\n -> groupPrefix ++ n) $
getGroupsFrom otherSidePath layout1
mapM_ (\n -> move' (n,activeNotebook)) groupNames
-- 2. Remove unused notebook from admin
ppNb <- getPanePathFromNB
let ! newMap = Map.delete otherSideNotebook ppNb
setPanePathFromNB newMap
-- 3. Remove one level and reparent notebook
mbParent <- liftIO $ widgetGetParent activeNotebook
case mbParent of
Nothing -> error "collapse: no parent"
Just parent -> do
mbGrandparent <- liftIO $ widgetGetParent parent
case mbGrandparent of
Nothing -> error "collapse: no grandparent"
Just grandparent -> do
nbIndex <- if grandparent `isA` gTypeNotebook
then liftIO $ notebookPageNum ((castToNotebook' "viewCollapse'' 1") grandparent) parent
else return Nothing
liftIO $ containerRemove (castToContainer grandparent) parent
liftIO $ containerRemove (castToContainer parent) activeNotebook
if length panePath > 1
then do
let lasPathElem = last newPanePath
case (lasPathElem, nbIndex) of
(SplitP dir, _) | dir == TopP || dir == LeftP ->
liftIO $ panedPack1 (castToPaned grandparent) activeNotebook True True
(SplitP dir, _) | dir == BottomP || dir == RightP ->
liftIO $ panedPack2 (castToPaned grandparent) activeNotebook True True
(GroupP group, Just n) -> do
liftIO $ notebookInsertPage ((castToNotebook' "viewCollapse'' 2") grandparent) activeNotebook group n
label <- groupLabel group
liftIO $ do
notebookSetTabLabel ((castToNotebook' "viewCollapse'' 3") grandparent) activeNotebook label
notebookSetCurrentPage ((castToNotebook' "viewCollapse'' 4") grandparent) n
return ()
_ -> error "collapse: Unable to find page index"
liftIO $ widgetSetName activeNotebook $panePathElementToWidgetName lasPathElem
else liftIO $ do
boxPackStart (castToVBox grandparent) activeNotebook PackGrow 0
boxReorderChild (castToVBox grandparent) activeNotebook 2
widgetSetName activeNotebook "root"
-- 4. Change panePathFromNotebook
adjustNotebooks panePath newPanePath
-- 5. Change paneMap
adjustPanes panePath newPanePath
-- 6. Change layout
adjustLayoutForCollapse panePath
getGroupsFrom :: PanePath -> PaneLayout -> [String]
getGroupsFrom path layout =
case layoutFromPath path layout of
t@(TerminalP _ _ _ _ _) -> Map.keys (paneGroups t)
HorizontalP _ _ _ -> []
VerticalP _ _ _ -> []
viewNewGroup :: StateM ()
viewNewGroup = do
mainWindow <- getMainWindow
mbGroupName <- liftIO $ groupNameDialog mainWindow
case
mbGroupName of
Just groupName -> do
layout <- getLayoutSt
if groupName `Set.member` allGroupNames layout
then liftIO $ do
md <- messageDialogNew (Just mainWindow) [] MessageWarning ButtonsClose
("Group name not unique " ++ groupName)
dialogRun md
widgetDestroy md
return ()
else viewNest groupName
Nothing -> return ()
newGroupOrBringToFront :: String -> PanePath -> StateM (Maybe PanePath,Bool)
newGroupOrBringToFront groupName pp = do
layout <- getLayoutSt
if groupName `Set.member` allGroupNames layout
then do
mbPP <- bringGroupToFront groupName
return (mbPP,False)
else let realPath = getBestPanePath pp layout in do
viewNest' realPath groupName
return (Just (realPath ++ [GroupP groupName]),True)
bringGroupToFront :: String -> StateM (Maybe PanePath)
bringGroupToFront groupName = do
layout <- getLayoutSt
case findGroupPath groupName layout of
Just path -> do
widget <- getNotebookOrPaned path castToWidget
liftIO $ setCurrentNotebookPages widget
return (Just path)
Nothing -> return Nothing
-- Yet another stupid little dialog
groupNameDialog :: Window -> IO (Maybe String)
groupNameDialog parent = liftIO $ do
dia <- dialogNew
windowSetTransientFor dia parent
windowSetTitle dia "Enter group name"
upper <- dialogGetUpper dia
lower <- dialogGetActionArea dia
buttonBox <- hButtonBoxNew
okButton <- buttonNewFromStock "gtk-ok"
cancelButton <- buttonNewFromStock "gtk-cancel"
onClicked okButton (dialogResponse dia ResponseOk)
onClicked cancelButton (dialogResponse dia ResponseCancel)
boxPackStartDefaults buttonBox cancelButton
boxPackStartDefaults buttonBox okButton
boxPackStart lower buttonBox PackNatural 7
dialogSetDefaultResponse dia ResponseOk
label <- labelNew (Just "Group Name")
textField <- entryNew
-- entrySetActivatesDefault textField True
windowSetDefault dia (Just okButton)
boxPackStartDefaults upper label
boxPackStartDefaults upper textField
widgetShowAll dia
resp <- dialogRun dia
value <- entryGetText textField
widgetDestroy dia
case resp of
ResponseOk | value /= "" -> return (Just value)
_ -> return Nothing
viewNest :: String -> StateM ()
viewNest group = do
mbPanePath <- getActivePanePath
case mbPanePath of
Nothing -> return ()
Just panePath -> do
viewNest' panePath group
viewNest' :: PanePath -> String -> StateM ()
viewNest' panePath group = do
activeNotebook <- (getNotebook' "viewNest' 1") panePath
mbParent <- liftIO $ widgetGetParent activeNotebook
case mbParent of
Nothing -> return ()
Just parent -> do
layout <- getLayoutSt
let paneLayout = layoutFromPath panePath layout
case paneLayout of
(TerminalP {}) -> do
nb <- newNotebook (panePath ++ [GroupP group])
liftIO $ widgetSetName nb (groupPrefix ++ group)
notebookInsertOrdered activeNotebook nb group Nothing True
liftIO $ widgetShowAll nb
--widgetGrabFocus activeNotebook
handleFunc <- runInIO (handleNotebookSwitch nb)
liftIO $ afterSwitchPage nb handleFunc
adjustLayoutForNest group panePath
_ -> return ()
viewDetach :: StateM (Maybe (Window,Widget))
viewDetach = do
id <- liftIO $ fmap show getCPUTime
mbPanePath <- getActivePanePath
case mbPanePath of
Nothing -> return Nothing
Just panePath -> do
viewDetach' panePath id
viewDetach' :: PanePath -> String -> StateM (Maybe (Window,Widget))
viewDetach' panePath id = do
activeNotebook <- (getNotebook' "viewDetach'") panePath
mbParent <- liftIO $ widgetGetParent activeNotebook
case mbParent of
Nothing -> return Nothing
Just parent -> do
layout <- getLayoutSt
let paneLayout = layoutFromPath panePath layout
case paneLayout of
(TerminalP{detachedSize = size}) -> do
window <- liftIO $ do
window <- windowNew
windowSetTitle window "Leksah detached window"
widgetSetName window id
case size of
Just (width, height) -> do
windowSetDefaultSize window width height
Nothing -> do
(curWidth, curHeight) <- widgetGetSize activeNotebook
windowSetDefaultSize window curWidth curHeight
containerRemove (castToContainer parent) activeNotebook
containerAdd window activeNotebook
widgetShowAll window
return window
handleFunc <- runInIO (handleReattach id window)
liftIO $ window `onDelete` handleFunc
windows <- getWindowsSt
setWindowsSt $ windows ++ [window]
adjustLayoutForDetach id panePath
return (Just (window, castToWidget activeNotebook))
_ -> return Nothing
handleReattach :: String -> Window -> Event -> StateM Bool
handleReattach windowId window _ = do
layout <- getLayoutSt
case findDetachedPath windowId layout of
Nothing -> trace ("ViewFrame>>handleReattach: panePath for id not found: " ++ windowId)
$ do
windows <- getWindowsSt
setWindowsSt $ delete window windows
return False
Just pp -> do
nb <- (getNotebook' "handleReattach") pp
parent <- getNotebookOrPaned (init pp) castToContainer
liftIO $ containerRemove (castToContainer window) nb
liftIO $ containerAdd parent nb
adjustLayoutForReattach pp
windows <- getWindowsSt
setWindowsSt $ delete window windows
case last pp of
GroupP groupName -> do
label <- groupLabel groupName
liftIO $ notebookSetTabLabel ((castToNotebook' "handleReattach") parent) nb label
otherwise -> return ()
return False -- "now destroy the window"
groupMenuLabel :: String -> StateM (Maybe Label)
groupMenuLabel group = liftM Just (liftIO $ labelNew (Just group))
handleNotebookSwitch :: Notebook -> Int -> StateM ()
handleNotebookSwitch nb index = do
mbW <- liftIO $ notebookGetNthPage nb index
case mbW of
Nothing -> error "ViewFrame/handleNotebookSwitch: Can't find widget"
Just w -> do
name <- liftIO $ widgetGetName w
mbPane <- findPaneFor name
case mbPane of
Nothing -> return ()
Just (PaneC p) -> makeActive p []
where
findPaneFor :: String -> StateM (Maybe GenPane)
findPaneFor n1 = do
panes' <- getPanesSt
foldM (\r (PaneC p) -> do
n2 <- liftIO $ widgetGetName (getTopWidget p)
return (if n1 == n2 then (Just (PaneC p)) else r))
Nothing (Map.elems panes')
--
-- | Moves the activePane in the given direction, if possible
-- | If their are many possibilities choose the leftmost and topmost
--
viewMove :: PaneDirection -> StateM ()
viewMove direction = do
mbPane <- getActivePaneSt
case mbPane of
Nothing -> do
return ()
Just (paneName,_) -> do
(PaneC pane) <- paneFromName paneName
mbPanePath <- getActivePanePath
case mbPanePath of
Nothing -> do
return ()
Just panePath -> do
layout <- getLayoutSt
case findMoveTarget panePath layout direction of
Nothing -> do
return ()
Just moveTo -> move moveTo pane
--
-- | Find the target for a move
--
findMoveTarget :: PanePath -> PaneLayout -> PaneDirection -> Maybe PanePath
findMoveTarget panePath layout direction=
let oppositeDir = otherDirection direction
canMove [] = []
canMove reversedPath =
case head reversedPath of
SplitP d | d == oppositeDir
-> SplitP direction : (tail reversedPath)
GroupP group -> []
_ -> canMove (tail reversedPath)
basePath = reverse (canMove $ reverse panePath)
in case basePath of
[] -> Nothing
_ -> let layoutP = layoutFromPath basePath layout
in Just $basePath ++ findAppropriate layoutP oppositeDir
--
-- | Moves the given Pane to the given path
--
move :: Pane alpha => PanePath -> alpha -> StateM ()
move toPanePath pane = do
let name = paneName pane
toNB <- (getNotebook' "move") toPanePath
move' (name,toNB)
--
-- | Moves the given Pane to the given path, care for groups (layout, paneMap)
--
move' :: (PaneName,Notebook) -> StateM ()
move' (paneName,toNB) = do
paneMap <- getPaneMapSt
panes <- getPanesSt
layout <- getLayoutSt
case groupPrefix `stripPrefix` paneName of
Just group -> do
case findGroupPath group layout of
Nothing -> trace ("ViewFrame>>move': group not found: " ++ group) return ()
Just fromPath -> do
groupNBOrPaned <- getNotebookOrPaned fromPath castToWidget
fromNB <- (getNotebook' "move'") (init fromPath)
ppNb <- getPanePathFromNB
case toNB `Map.lookup` ppNb of
Nothing -> trace "ViewFrame>>move': panepath for Notebook not found1" return ()
Just toPath -> do
when (fromNB /= toNB && not (isPrefixOf fromPath toPath)) $ do
mbNum <- liftIO $ notebookPageNum fromNB groupNBOrPaned
case mbNum of
Nothing -> trace "ViewFrame>>move': group notebook not found" return ()
Just num -> do
liftIO $ notebookRemovePage fromNB num
label <- groupLabel group
notebookInsertOrdered toNB groupNBOrPaned group Nothing True
liftIO $ notebookSetTabLabel toNB groupNBOrPaned label
adjustPanes fromPath (toPath ++ [GroupP group])
adjustLayoutForGroupMove fromPath toPath group
adjustNotebooks fromPath (toPath ++ [GroupP group])
layout2 <- getLayoutSt
return ()
Nothing ->
case paneName `Map.lookup` panes of
Nothing -> trace ("ViewFrame>>move': pane not found: " ++ paneName) return ()
Just (PaneC pane) -> do
ppNb <- getPanePathFromNB
case toNB `Map.lookup` ppNb of
Nothing -> trace "ViewFrame>>move': panepath for Notebook not found2" return ()
Just toPath ->
case paneName `Map.lookup`paneMap of
Nothing -> trace ("ViewFrame>>move': pane data not found: " ++ paneName)
return ()
Just (fromPath,_) -> do
let child = getTopWidget pane
(fromPane,cid) <- guiPropertiesFromName paneName
fromNB <- (getNotebook' "move'") fromPane
when (fromNB /= toNB) $ do
mbNum <- liftIO $ notebookPageNum fromNB child
case mbNum of
Nothing -> trace "ViewFrame>>move': widget not found" return ()
Just num -> do
liftIO $ notebookRemovePage fromNB num
notebookInsertOrdered toNB child paneName Nothing False
let paneMap1 = Map.delete paneName paneMap
setPaneMapSt $ Map.insert paneName (toPath,cid) paneMap1
findAppropriate :: PaneLayout -> PaneDirection -> PanePath
findAppropriate (TerminalP {}) _ = []
findAppropriate (HorizontalP t b _) LeftP = SplitP TopP : findAppropriate t LeftP
findAppropriate (HorizontalP t b _) RightP = SplitP TopP : findAppropriate t RightP
findAppropriate (HorizontalP t b _) BottomP = SplitP BottomP : findAppropriate b BottomP
findAppropriate (HorizontalP t b _) TopP = SplitP TopP : findAppropriate b TopP
findAppropriate (VerticalP l r _) LeftP = SplitP LeftP : findAppropriate l LeftP
findAppropriate (VerticalP l r _) RightP = SplitP RightP : findAppropriate r RightP
findAppropriate (VerticalP l r _) BottomP = SplitP LeftP : findAppropriate l BottomP
findAppropriate (VerticalP l r _) TopP = SplitP LeftP : findAppropriate l TopP
--
-- | Construct a new notebook
--
newNotebook' :: IO Notebook
newNotebook' = do
nb <- notebookNew
notebookSetTabPos nb PosTop
notebookSetShowTabs nb True
notebookSetScrollable nb True
notebookSetPopup nb True
return nb
--
-- | Construct a new notebook,
--
newNotebook :: PanePath -> StateM Notebook
newNotebook pp = do
nb <- liftIO newNotebook'
ppNb <- getPanePathFromNB
setPanePathFromNB $ Map.insert nb pp ppNb
func <- runInIO move'
liftIO $ do
tl <- targetListNew
targetListAddTextTargets tl 0
dragDestSet nb [DestDefaultAll] [ActionCopy, ActionMove]
dragDestSetTargetList nb tl
on nb dragDataReceived (dragFunc nb func)
return nb
where
dragFunc ::
Notebook ->
((PaneName,Notebook) -> IO ()) ->
DragContext ->
Point ->
InfoId ->
TimeStamp ->
(SelectionDataM ())
dragFunc nb func cont point id timeStamp = do
mbText <- selectionDataGetText
case mbText of
Nothing -> return ()
Just str -> do
liftIO $ func (str,nb)
return ()
findDetachedPath :: String -> PaneLayout -> Maybe PanePath
findDetachedPath id layout =
let terminalPairs = terminalsWithPanePath layout
in case (filter filterFunc terminalPairs) of
[] -> Nothing
(pp,_) : [] -> Just pp
_ -> error ("ViewFrame>>window id not unique: " ++ id)
where
filterFunc (_,(TerminalP _ _ _ (Just lid) _)) = lid == id
filterFunc _ = False
allGroupNames :: PaneLayout -> Set String
allGroupNames pl = Set.unions $ map getFunc (terminalsWithPanePath pl)
where
getFunc (_,(TerminalP groups _ _ _ _)) = Map.keysSet groups
getFunc _ = error "ViewFrame>>allGroupNames: impossible"
--
-- | Get another pane path which points to the other side at the same level
--
otherSide :: PanePath -> Maybe PanePath
otherSide [] = Nothing
otherSide p = let rp = reverse p
in case head rp of
SplitP d -> Just (reverse $ SplitP (otherDirection d) : tail rp)
_ -> Nothing
--
-- | Get the opposite direction of a pane direction
--
otherDirection :: PaneDirection -> PaneDirection
otherDirection LeftP = RightP
otherDirection RightP = LeftP
otherDirection TopP = BottomP
otherDirection BottomP = TopP
--
-- | Get the (gtk) Paned widget for a given path
--
getPaned :: PanePath -> StateM Paned
getPaned p = getNotebookOrPaned p castToPaned
--
-- | Get the path to the active pane
--
getActivePanePath :: StateM (Maybe PanePath)
getActivePanePath = do
mbPane <- getActivePaneSt
case mbPane of
Nothing -> return Nothing
Just (paneName,_) -> do
(pp,_) <- guiPropertiesFromName paneName
return (Just pp)
getActivePanePathOrStandard :: StandardPath -> StateM (PanePath)
getActivePanePathOrStandard sp = do
mbApp <- getActivePanePath
case mbApp of
Just app -> return app
Nothing -> do
layout <- getLayoutSt
return (getBestPanePath sp layout)
--
-- | Get the active notebook
--
getActiveNotebook :: StateM (Maybe Notebook)
getActiveNotebook = do
mbPanePath <- getActivePanePath
case mbPanePath of
Just panePath -> do
nb <- (getNotebook' "getActiveNotebook") panePath
return (Just nb)
Nothing -> return Nothing
--
-- | Changes a pane path in the pane map
--
adjustPanes :: PanePath -> PanePath -> StateM ()
adjustPanes fromPane toPane = do
paneMap <- getPaneMapSt
setPaneMapSt (Map.map (\(pp,other) ->
case stripPrefix fromPane pp of
Just rest -> (toPane ++ rest,other)
_ -> (pp,other)) paneMap)
adjustNotebooks :: PanePath -> PanePath -> StateM ()
adjustNotebooks fromPane toPane = do
npMap <- trace ("+++ adjustNotebooks from: " ++ show fromPane ++ " to " ++ show toPane)
getPanePathFromNB
setPanePathFromNB (Map.map (\pp ->
case stripPrefix fromPane pp of
Just rest -> toPane ++ rest
_ -> pp) npMap)
--
-- | Changes the layout for a split
--
adjustLayoutForSplit :: PaneDirection -> PanePath -> StateM ()
adjustLayoutForSplit dir path = do
layout <- getLayoutSt
let paneLayout = layoutFromPath path layout
newLayout = TerminalP Map.empty Nothing 0 Nothing Nothing
newTerm = case dir of
LeftP -> VerticalP paneLayout newLayout 0
RightP -> VerticalP newLayout paneLayout 0
TopP -> HorizontalP paneLayout newLayout 0
BottomP -> HorizontalP newLayout paneLayout 0
setLayoutSt $ adjustLayout path layout newTerm
--
-- | Changes the layout for a nest
--
adjustLayoutForNest :: String -> PanePath -> StateM ()
adjustLayoutForNest group path = do
layout <- getLayoutSt
let paneLayout = layoutFromPath path layout
newTerm = case paneLayout of
(TerminalP {paneGroups = groups}) -> paneLayout {
paneGroups = Map.insert group (TerminalP Map.empty Nothing 0 Nothing Nothing) groups}
_ -> error "Unexpected layout type in adjustLayoutForNest"
setLayoutSt $ adjustLayout path layout newTerm
--
-- | Changes the layout for a detach
--
adjustLayoutForDetach :: String -> PanePath -> StateM ()
adjustLayoutForDetach id path = do
layout <- getLayoutSt
let paneLayout = layoutFromPath path layout
newTerm = case paneLayout of
(TerminalP {}) -> paneLayout {detachedId = Just id}
_ -> error "Unexpected layout type in adjustLayoutForDetach"
setLayoutSt $ adjustLayout path layout newTerm
--
-- | Changes the layout for a reattach
--
adjustLayoutForReattach :: PanePath -> StateM ()
adjustLayoutForReattach path = do
layout <- getLayoutSt
let paneLayout = layoutFromPath path layout
newTerm = case paneLayout of
(TerminalP {}) -> paneLayout {detachedId = Nothing, detachedSize = Nothing}
_ -> error "Unexpected layout type in adjustLayoutForReattach"
setLayoutSt $ adjustLayout path layout newTerm
--
-- | Changes the layout for a collapse
--
adjustLayoutForCollapse :: PanePath -> StateM ()
adjustLayoutForCollapse oldPath = do
layout <- getLayoutSt
let pathLayout = layoutFromPath oldPath layout
setLayoutSt $ adjustLayout (init oldPath) layout pathLayout
--
-- | Changes the layout for a move
--
adjustLayoutForGroupMove :: PanePath -> PanePath -> String -> StateM ()
adjustLayoutForGroupMove fromPath toPath group = do
layout <- getLayoutSt
let layoutToMove = layoutFromPath fromPath layout
let newLayout = removeGL fromPath layout
setLayoutSt (addGL layoutToMove (toPath ++ [GroupP group]) newLayout)
--
-- | Changes the layout for a remove
--
adjustLayoutForGroupRemove :: PanePath -> String -> StateM ()
adjustLayoutForGroupRemove fromPath group = do
layout <- getLayoutSt
setLayoutSt (removeGL fromPath layout)
--
-- | Add group layout at a certain path
--
addGL :: PaneLayout -> PanePath -> PaneLayout -> PaneLayout
addGL toAdd [GroupP group] t@(TerminalP oldGroups _ _ _ _) = t{paneGroups = Map.insert group toAdd oldGroups}
addGL toAdd (GroupP group:r) old@(TerminalP {paneGroups = groups})
| group `Map.member` groups = old{paneGroups = Map.adjust (addGL toAdd r) group groups}
addGL toAdd (SplitP TopP:r) (HorizontalP tp bp _) = HorizontalP (addGL toAdd r tp) bp 0
addGL toAdd (SplitP BottomP:r) (HorizontalP tp bp _) = HorizontalP tp (addGL toAdd r bp) 0
addGL toAdd (SplitP LeftP:r) (VerticalP lp rp _) = VerticalP (addGL toAdd r lp) rp 0
addGL toAdd (SplitP RightP:r) (VerticalP lp rp _) = VerticalP lp (addGL toAdd r rp) 0
addGL _ p l = error $"ViewFrame>>addGL: inconsistent layout" ++ show p ++ " " ++ show l
--
-- | Changes the layout by replacing element at pane path (pp) with replace
--
adjustLayout :: PanePath -> PaneLayout -> PaneLayout -> PaneLayout
adjustLayout pp layout replace = adjust' pp layout
where
adjust' [] _ = replace
adjust' (GroupP group:r) old@(TerminalP {paneGroups = groups})
| group `Map.member` groups =
old{paneGroups = Map.adjust (adjustPaneGroupLayout r) group groups}
adjust' (SplitP TopP:r) (HorizontalP tp bp _) = HorizontalP (adjust' r tp) bp 0
adjust' (SplitP BottomP:r) (HorizontalP tp bp _) = HorizontalP tp (adjust' r bp) 0
adjust' (SplitP LeftP:r) (VerticalP lp rp _) = VerticalP (adjust' r lp) rp 0
adjust' (SplitP RightP:r) (VerticalP lp rp _) = VerticalP lp (adjust' r rp) 0
adjust' p l = error $"inconsistent layout (adjust) " ++ show p ++ " " ++ show l
adjustPaneGroupLayout p group = adjust' p group
widgetGetRel :: Widget -> [String] -> (Widget -> b) -> IO (b)
widgetGetRel w sl cf = do
r <- widgetFromPath w sl
return (cf r)
getUIAction :: String -> (Action -> a) -> StateM (a)
getUIAction str f = do
uiManager <- getUiManagerSt
liftIO $ do
findAction <- uiManagerGetAction uiManager str
case findAction of
Just act -> return (f act)
Nothing -> error $"getUIAction can't find action " ++ str