{-| Module to generate an expandible tree of directories contained within the file system. -} module DirectoryTree ( addSubTree, getPathAtActivation, getListing, expandPath ) where import Graphics.UI.Gtk import Data.Tree import Util import Popup import qualified Control.Exception as E import System.Directory ( getDirectoryContents, doesDirectoryExist ) import System.FilePath ( (), splitFileName, dropTrailingPathSeparator, splitDirectories ) import System.Posix.Files ( getSymbolicLinkStatus, isSymbolicLink, fileAccess ) import Control.Monad ( filterM, forM, forM_ ) import List ( isPrefixOf, sort ) {-| Calculates a string based on where a user has activated a row of a 'TreeStore'. It recursively scans through the tree path getting the string at each seperate node leading up to the selected node. -} getPathAtActivation :: TreePath -- ^ The path to the node selected at activation. -> TreeStore String -- ^ A store of strings. -> IO String -- ^ The selected string. getPathAtActivation path store | null ( init path ) = treeStoreGetValue store path >>= return | otherwise = ( treeStoreGetValue store path ) >>= ( \l -> ( getPathAtActivation ( init path ) store ) >>= ( \f -> return ( "/" ++ ( reverse ( dropTrailingPathSeparator ( reverse ( f ++ l ) ) ) ) ) ) ) {-| Scans to the selected node, that the user has requested be expanded. Once it gets there it checks to see whether or not the node already contains elements. If so, it does nothing, but if it is empty it adds in a sub tree containing the selected folders sub directories. -} addSubTree :: TreePath -- ^ The path to the node to be expanded. -> TreeStore String -- ^ The 'TreeStore' containing the selected node. -> IO ( ) addSubTree path store = do newtree <- getPathAtActivation path store >>= dirContents tree <- treeStoreGetTree store path case ( ( rootLabel ( ( subForest tree ) !! 0 ) ) == ("") ) of True -> do forM_ ( reverse ( subForest newtree ) ) $ \sub -> treeStoreInsertTree store path 0 sub ( treeStoreRemove store ( path ++ [length ( subForest newtree)] ) ) >> return ( ) False -> return ( ) {-| Expands the tree to match a supplied file path, if it it exist within the treeStore. -} expandPath :: TreeView -- ^ The treeView containing the treeStore -> TreeStore String -- ^ The tree store containing the directories. -> FilePath -- ^ The path to be expanded to. -> IO ( ) expandPath tree store path = do treeS <- treeStoreGetTree store [0] let dirs = "/" : ( map ( "/" ++ ) ( tail ( splitDirectories path ) ) ) paths = findPath [treeS] dirs case ( length paths == length dirs ) of False -> do addSubTree paths store expandPath tree store path True -> do treeViewCollapseAll tree treeViewExpandToPath tree paths treeViewSetCursor tree paths Nothing {-| Function to find the path to a list of 'a' contained in a Forest. -} findPath :: Eq a => Forest a -- ^ Forest of data. -> [a] -- ^ List containing elements to make a path. -> TreePath -- ^ Resulting path to elements of [a] findPath [] _ = [] findPath _ [] = [] findPath (x:xs) (y:ys) | rootLabel x == y = 0 : findPath ( subForest x ) ys | otherwise = case res of [] -> [] (x:[]) -> 1 + x : [] (x:xs) -> 1 + x : xs where res = findPath xs (y:ys) {-| Function to calculate the amount of directories within a directory. -} subDirSize :: FilePath -- ^ The path to the directory of which size is to be calculated. -> IO Int -- ^ The amount of accessible directories within the directory. subDirSize dir = getDirectoryContents dir >>= ( \c -> filterM doesDirectoryExist ( map ( dir ) ( filter ( not . isPrefixOf "." ) c ) ) ) >>= filterLinks >>= (\d -> return ( length d ) ) {-| Function to get the directory contents and add them to an expanding Tree, with sub-directoryies added as nodes, viewable on expansion. It only shows accessible folders with sub-directories as expandible. -} dirContents :: FilePath -- ^ The path to the folder to be "treed" -> IO ( Tree String ) -- ^ A tree of String type, containing nodes representing sub-directories. dirContents path = do let (_, title) = splitFileName path nodes <- ( getDirectoryContents path >>= ( \c -> filterM doesDirectoryExist ( map ( path ) ( filter ( not . isPrefixOf "." ) c ) ) ) ) >>= filterLinks >>= ( \dirs -> forM ( sort dirs ) $ \d -> do let (_ , dname) = splitFileName d subDirSize d >>= ( \s -> return ( Node { rootLabel = "/" ++ dname, subForest = case s of 0 -> [] _ -> [Node { rootLabel = "", subForest = [] } ] } ) ) ) return ( Node { rootLabel = ( "/" ++ title ), subForest = nodes } ) {-| Function to generate an expandible tree listing of all directories on the users machine. -} getListing :: IO ( TreeView, ScrolledWindow, TreeStore String ) -- ^ The 'TreeView', 'ScrolledWindow', 'TreeStore' associated with the expandible tree. getListing = do scroll <- scrolledWindowNew Nothing Nothing store <- treeStoreNew [Node { rootLabel = "/", subForest = [Node { rootLabel = "", subForest = [] } ] }] view <- treeViewNewWithModel store treeViewSetHeadersVisible view True col1 <- treeViewColumnNew treeViewColumnSetTitle col1 "FileSystem" renderer1 <- cellRendererTextNew cellLayoutPackStart col1 renderer1 True cellLayoutSetAttributes col1 renderer1 store $ \row -> [ cellText := row ] treeViewAppendColumn view col1 selection <- treeViewGetSelection view treeSelectionSetMode selection SelectionSingle containerAdd scroll view return ( view, scroll, store )