module GtkExtras.LargeTreeStore (
TreeStore,
treeStoreNew,
treeStoreNewDND,
treeStoreDefaultDragSourceIface,
treeStoreDefaultDragDestIface,
treeStoreGetValue,
treeStoreSetValue,
treeStoreChange,
treeStoreChangeM,
treeStoreInsertForest,
treeStoreInsert,
treeStoreInsertTree,
treeStoreRemove,
treeStoreClear,
treeStoreGetTree,
) where
import Control.Monad (liftM, void, when)
import Control.Monad.Trans (liftIO)
import Data.Functor ((<$>))
import Data.IORef
import Data.Maybe (fromJust)
import Data.NestedSet
import Data.Tree
import Foreign.C.Types (CInt (..))
import Graphics.UI.Gtk.ModelView.CustomStore
import Graphics.UI.Gtk.ModelView.TreeDrag
import Graphics.UI.Gtk.ModelView.TreeModel
import System.Glib.GObject
newtype TreeStore a = TreeStore (CustomStore (IORef (Store a)) a)
instance TypedTreeModelClass TreeStore
instance TreeModelClass (TreeStore a)
instance GObjectClass (TreeStore a) where
toGObject (TreeStore tm) = toGObject tm
unsafeCastGObject = TreeStore . unsafeCastGObject
data Store a = Store {
nestedSets :: NestedSets a
}
treeStoreNew :: Forest a -> IO (TreeStore a)
treeStoreNew forest = treeStoreNewDND forest
(Just treeStoreDefaultDragSourceIface)
(Just treeStoreDefaultDragDestIface)
treeStoreNewDND :: Forest a
-> Maybe (DragSourceIface TreeStore a)
-> Maybe (DragDestIface TreeStore a)
-> IO (TreeStore a)
treeStoreNewDND forest mDSource mDDest = do
storeRef <- newIORef Store {
nestedSets = forestToNestedSets forest
}
let withStore f = liftM f $ readIORef storeRef
customStoreNew storeRef TreeStore TreeModelIface {
treeModelIfaceGetFlags = return [],
treeModelIfaceGetIter = \path -> withStore $
\Store { nestedSets = sets } -> fromPath sets path,
treeModelIfaceGetPath = \iter -> withStore $
\Store { nestedSets = sets } -> toPath sets iter,
treeModelIfaceGetRow = withStore . getIterValueInStore,
treeModelIfaceIterNext = \iter -> withStore $
\Store { nestedSets = sets } ->
fmap positionToIter $ nestedSetsNextSiblingPosition sets . positionFromIter $ iter,
treeModelIfaceIterChildren = maybe (return $ Just invalidIter)
(\iter -> withStore $
\Store { nestedSets = sets } ->
fmap positionToIter $ nestedSetsFirstChildPosition sets . positionFromIter $ iter),
treeModelIfaceIterHasChild = \iter -> withStore $
\Store { nestedSets = sets } -> not . null . children . fromJust . nestedSetByPath sets . toPath sets $ iter,
treeModelIfaceIterNChildren = maybe
(withStore $ \Store { nestedSets = sets } -> length sets)
(\iter -> withStore $
\Store { nestedSets = sets } -> length . children . fromJust . nestedSetByPath sets . toPath sets $ iter),
treeModelIfaceIterNthChild = \mIter idx -> maybe
(withStore $ \Store { nestedSets = sets } -> fromPath sets [idx])
(\iter -> withStore $
\Store { nestedSets = sets } -> fromPath sets (toPath sets iter ++ [idx]))
mIter,
treeModelIfaceIterParent = \iter -> withStore $
\Store { nestedSets = sets } ->
fmap positionToIter $ nestedSetsParentPosition sets . positionFromIter $ iter,
treeModelIfaceRefNode = \_ -> return (),
treeModelIfaceUnrefNode = \_ -> return ()
} mDSource mDDest
treeStoreDefaultDragSourceIface :: DragSourceIface TreeStore row
treeStoreDefaultDragSourceIface = DragSourceIface {
treeDragSourceRowDraggable = \_ _-> return True,
treeDragSourceDragDataGet = treeSetRowDragData,
treeDragSourceDragDataDelete = \model dest@(_:_) -> do
_ <- liftIO $ treeStoreRemove model dest
return True
}
treeStoreDefaultDragDestIface :: DragDestIface TreeStore row
treeStoreDefaultDragDestIface = DragDestIface {
treeDragDestRowDropPossible = \model _ -> do
mModelPath <- treeGetRowDragData
case mModelPath of
Nothing -> return False
Just (model', _) -> return (toTreeModel model==toTreeModel model'),
treeDragDestDragDataReceived = \model dest@(_:_) -> do
mModelPath <- treeGetRowDragData
case mModelPath of
Nothing -> return False
Just (model', source) ->
if toTreeModel model /= toTreeModel model' then return False
else liftIO $ do
row <- treeStoreGetTree model source
treeStoreInsertTree model (init dest) (last dest) row
return True
}
invalidIter :: TreeIter
invalidIter = TreeIter 0 0 0 0
treeIterSetStamp :: TreeIter -> CInt -> TreeIter
treeIterSetStamp (TreeIter _ a b c) s = TreeIter s a b c
positionFromIter :: TreeIter -> Position
positionFromIter (TreeIter _ _ left right) = (fromIntegral left, fromIntegral right)
positionToIter :: Position -> TreeIter
positionToIter = setPositionToIter invalidIter
setPositionToIter :: TreeIter -> Position -> TreeIter
setPositionToIter (TreeIter stamp a _ _) (left, right) = TreeIter stamp a (fromIntegral left) (fromIntegral right)
toPath :: NestedSets a -> TreeIter -> TreePath
toPath sets iter = positionToPath sets (positionFromIter iter) 0
where positionToPath [] _ _ = []
positionToPath (first : ds) pos nr
| position first == pos = [nr]
| isNestedSetsPositionParent (position first) pos = nr:positionToPath (children first) pos 0
| otherwise = positionToPath ds pos (nr+1)
fromPath :: NestedSets a -> TreePath -> Maybe TreeIter
fromPath sets path = positionToIter <$> positionFromPath
where positionFromPath = position <$> nestedSetByPath sets path
getIterValueInStore :: TreeIter -> Store a -> a
getIterValueInStore iter (Store{nestedSets = sets}) = content . fromJust . nestedSetByPath sets . toPath sets $ iter
treeStoreInsertForest ::
TreeStore a
-> TreePath
-> Int
-> Forest a
-> IO ()
treeStoreInsertForest (TreeStore model) path pos nodes = do
customStoreInvalidateIters model
(idx, toggle) <- atomicModifyIORef (customStoreGetPrivate model) $
\Store { nestedSets = sets } ->
case insertIntoForest (nestedSetsToForest sets) nodes path pos of
Nothing -> error ("treeStoreInsertForest: path does not exist " ++ show path)
Just (newForest, idx, toggle) -> (Store { nestedSets = forestToNestedSets newForest }, (idx, toggle))
Store { nestedSets = sets } <- readIORef (customStoreGetPrivate model)
let rpath = reverse path
stamp <- customStoreGetStamp model
sequence_ [ let p' = reverse p
Just iter = fromPath sets p'
in treeModelRowInserted model p' (treeIterSetStamp iter stamp)
| (i, node) <- zip [idx..] nodes
, p <- paths (i : rpath) node ]
when toggle $ emitRowChildToggledEvent stamp $ fromPath sets path
where paths :: TreePath -> Tree a -> [TreePath]
paths path' Node { subForest = ts } =
path' : concat [ paths (n:path') t | (n, t) <- zip [0..] ts ]
emitRowChildToggledEvent _ Nothing = return()
emitRowChildToggledEvent stamp (Just iter) = treeModelRowHasChildToggled model path (treeIterSetStamp iter stamp)
treeStoreInsertTree ::
TreeStore a
-> TreePath
-> Int
-> Tree a
-> IO ()
treeStoreInsertTree store path pos node =
treeStoreInsertForest store path pos [node]
treeStoreInsert ::
TreeStore a
-> TreePath
-> Int
-> a
-> IO ()
treeStoreInsert store path pos node =
treeStoreInsertForest store path pos [Node node []]
insertIntoForest :: Forest a -> Forest a -> TreePath -> Int ->
Maybe (Forest a, Int, Bool)
insertIntoForest forest nodes [] pos
| pos<0 = Just (forest++nodes, length forest, null forest)
| otherwise = Just (prev++nodes++next, length prev, null forest)
where (prev, next) = splitAt pos forest
insertIntoForest forest nodes (p:ps) pos = case splitAt p forest of
(_, []) -> Nothing
(prev, Node { rootLabel = val,
subForest = for}:next) ->
case insertIntoForest for nodes ps pos of
Nothing -> Nothing
Just (for', pos', toggle) -> Just (prev++Node { rootLabel = val,
subForest = for' }:next,
pos', toggle)
treeStoreRemove :: TreeStore a -> TreePath -> IO Bool
treeStoreRemove (TreeStore model) path = do
customStoreInvalidateIters model
(found, toggle) <- atomicModifyIORef (customStoreGetPrivate model) $
\store -> case deleteFromNestedSets (nestedSets store) path of
Nothing -> (store, (False, False))
Just (sets, toggle) -> (store{nestedSets = sets}, (True, toggle))
when found $ do
when (toggle && not (null path)) $ do
Store{nestedSets = sets} <- readIORef (customStoreGetPrivate model)
let parent = init path
Just iter = fromPath sets parent
stamp <- customStoreGetStamp model
treeModelRowHasChildToggled model parent (treeIterSetStamp iter stamp)
treeModelRowDeleted model path
return found
treeStoreClear :: TreeStore a -> IO ()
treeStoreClear (TreeStore model) = do
customStoreInvalidateIters model
Store { nestedSets = sets } <- readIORef (customStoreGetPrivate model)
writeIORef (customStoreGetPrivate model) Store {
nestedSets = forestToNestedSets []
}
let loop (1) = return ()
loop n = treeModelRowDeleted model [n] >> loop (n1)
loop (length sets 1)
deleteFromNestedSets :: NestedSets a -> TreePath -> Maybe (NestedSets a, Bool)
deleteFromNestedSets _ [] = Nothing
deleteFromNestedSets sets (p:ps) = case splitAt p sets of
(_, []) -> Nothing
(prev, node@NestedSetsNode{children = subSets}:next) ->
if null ps then Just (prev++next, null prev && null next) else
case deleteFromNestedSets subSets ps of
Nothing -> Nothing
Just (subSets', toggle) -> Just (prev++node{children=subSets'}:next, toggle)
treeStoreSetValue :: TreeStore a -> TreePath -> a -> IO ()
treeStoreSetValue store path value = void $ treeStoreChangeM store path (\_ -> return value)
treeStoreChange :: TreeStore a -> TreePath -> (a -> a) -> IO Bool
treeStoreChange store path func = treeStoreChangeM store path (return . func)
treeStoreChangeM :: TreeStore a -> TreePath -> (a -> IO a) -> IO Bool
treeStoreChangeM (TreeStore model) path act = do
customStoreInvalidateIters model
store@Store { nestedSets = sets } <- readIORef (customStoreGetPrivate model)
(store'@Store { nestedSets = sets' }, found) <- do
mRes <- changeNestedSets sets act path
return $ case mRes of
Nothing -> (store, False)
Just sets' -> (Store { nestedSets = sets' }, True)
when found $ writeIORef (customStoreGetPrivate model) store'
let Just iter = fromPath sets' path
stamp <- customStoreGetStamp model
when found $ treeModelRowChanged model path (treeIterSetStamp iter stamp)
return found
changeNestedSets :: NestedSets a -> (a -> IO a) -> TreePath -> IO (Maybe (NestedSets a))
changeNestedSets _ _ [] = return Nothing
changeNestedSets sets act (p:ps) = case splitAt p sets of
(_, []) -> return Nothing
(prev, node : next) -> do
node' <- (if null ps then updateLeaf else updateBranch) node
return $ fmap (mergeNode prev next) node'
where updateLeaf node@NestedSetsNode{content = val} = do
val' <- act val
return . Just $ node{content = val'}
updateBranch node@NestedSetsNode{children = subSets} = do
subSets' <- changeNestedSets subSets act ps
case subSets' of
Nothing -> return Nothing
Just subSets'' -> return . Just $ node{children = subSets''}
mergeNode prev next node = prev ++ node : next
treeStoreGetValue :: TreeStore a -> TreePath -> IO a
treeStoreGetValue (TreeStore model) path = do
Store { nestedSets = sets } <- readIORef (customStoreGetPrivate model)
return $ nestedSetValueByPath sets path
where nestedSetValueByPath sets = content . fromJust . nestedSetByPath sets
treeStoreGetTree :: TreeStore a -> TreePath -> IO (Tree a)
treeStoreGetTree (TreeStore model) path = do
Store { nestedSets = sets } <- readIORef (customStoreGetPrivate model)
return $ nestedSubtree . fromJust . nestedSetByPath sets $ path
where nestedSubtree node = Node (content node) (map nestedSubtree $ children node)
nestedSetByPath :: NestedSets a -> TreePath -> Maybe (NestedSetsNode a)
nestedSetByPath _ [] = Nothing
nestedSetByPath sets (first:rest) = (sets `maybeNth` first) >>= (`nestedSetChildrenByPath` rest)
where nestedSetChildrenByPath set ([]) = Just set
nestedSetChildrenByPath set (p:ds) = (children set `maybeNth` p) >>= (`nestedSetChildrenByPath` ds)
maybeNth :: [a] -> Int -> Maybe a
maybeNth [] _ = Nothing
maybeNth (r:_) 0 = Just r
maybeNth (_:rs) n = maybeNth rs (n1)