Mar 07

Just spent the last 2 days commenting all my code. That took a lot longer than I expected, but there was a lot to cover. I commented all the code using haddock style comments, even the functions that are hidden, as I think its a good way of commenting the code, and explaining the arguments to functions.

All the documentation is available here: Documentation

Now all that’s left is

  • Fix my search function.
  • Add in custom commands for opening programs.
  • Try to implement drag and drop functionality between the panes.
  • Get the directory tree to expand to the currently selected directory.

M

Tagged with:
Mar 05

To start with, I’ve created two types for my program, a StoreRow type and a Browser type, these have enabled me to make my code a bit more readable, and also to decrease the length of my function signatures :D , as some of them spanned 2 lines in my editor. It also allows me to work more freely, as I can pass the respective types around, taking what I need from them where I need it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
data StoreRow = StoreRow {
    name :: String,
    icon :: Pixbuf,
    size :: String,
    lastAccess :: String,
    permissions :: String }
 
data FileBrowser = FileBrowser {
    switch :: ToggleButton,
    location :: Entry,
    treeScroll :: ScrolledWindow,
    dirScroll :: ScrolledWindow,
    fileScroll :: ScrolledWindow,
    treeTree :: TreeView,
    dirTree :: TreeView,
    fileTree :: TreeView,
    treeStore :: TreeStore String,
    dirStore :: ListStore StoreRow,
    fileStore :: ListStore StoreRow }

Secondly, as stated before, I’m using xdg-open to open files, using System.Cmd. I’ve been messing around with it a bit today, and I thought before that if it couldn’t find a default application it still returned ExitSuccess to rawSystem, but, it actually doesn’t so what I plan to do now is, if it doesn’t find an application to open the passed file, pop up an error window, prompting the user to supply a command name to open it with, then write this to a file, along with the extension of the file being opened. Next time it happens, scan through the file, and if a command isn’t found to match the extension, query the user!

Problem solved…. In theory!

I’m going to start documenting the code using Haddock and I’ll upload that later.

M

Tagged with:
Mar 03

I spent the day today rewriting my SystemOperation module, to handle multiple selections. I got multiple selections working yesterday, but I felt it was done poorly, so out with the old in with the new. Due to reducing all my previous code as far as I could, I had to rewrite each function out again, i.e. renameFile, renameFolder etc etc, and then reduce down based on that.

I also added:

  • File Size information, 0 if directory
  • Last Modified Time
  • Permissions

To each element in both the directory and file tree.

This is the point where I would love to be able to actually set the size for the widgets myself, as the directory tree is taking up way too much space on screen, and is annoying the hell out of me. I spent the evening trawling the libraries for something that would let me resize it, on the off chance that they had included this functionality into the new library. No, would be the answer to that. So it’s all or nothing. I also reduced a fair bit of the rest of my code, a line or two here, a line or two there, so all in all, quite the productive day.

M

Tagged with:
Mar 01

Right, have all thee code changed to the new version of Gtk2HS… I am liking the new stuff, typed stores do save a lot of hassle when trying to get selected values back out of the store!

So the file manager, is pretty much finished now :D every addition from here is just an improvement, and I only have two things that I really want to do:

Multiple Selections:
Open multiple files, copy multiple files, delete multiple files, move multiple files.

Drag and drop:
Use the drag and drop functionality for moving multiple files/directories!!

M

Edit: To work in multiple selections means that I am going to have to restructure a lot of my code again :(

Tagged with:
Feb 25

Well I’m getting my head around this update to the library, and I am impressed. The idea of typed lists, while it seemed like a pain at first, because it meant I had to rewrite a lot of core bits of my code, is actually a great idea, and makes the code easier to read. But, even at that I’d say that it’ll take a week or so just to get it back to the same point, I can’t really see how I’m going to add the images to the lists dynamically, so that’ll be a nightmare. I’m also spotting more ways of reducing my code as I go along, and working that into it too.

I managed to delete most of my home folder earlier, don’t ask me how, but it was the first run of the code when I’d worked all the gtk2hs bugs out of it. Thank god for backups, and having the un-backed up stuff open in a text editor.

M

Tagged with:
Feb 25

Gtk2HS 0.1 has been release, and this now means I’m going to have to change quite a bit of my code, as they are using a new API for models :(

Tagged with:
Feb 24

Right, well the problem of the sub-trees being repetitively built was easy to fix:

  • Extract the tree at the path clicked on.
  • Extract the rootLabel of the first node of it’s sub-tree
  • Check if that is equal to “” – empty
  • If so: build the subtree, if not, continue without

1
2
3
4
5
6
7
8
9
addSubTree :: G.TreePath -> MVTree.TreeStore String -> IO ( )
addSubTree path store = do
        newtree <- getPathAtActivation path store >>= dirContentsTwo
        tree <- TS.treeStoreGetTree store path
        case ( ( rootLabel ( ( subForest tree ) !! 0 ) ) == ("") ) of
                True -> do
                        forM ( reverse ( subForest newtree ) ) $ \sub -> TS.treeStoreInsertTree store path 0 sub
                        ( TS.treeStoreRemove store ( path ++ [length ( subForest newtree)] ) ) >> return ( )
                False -> return ( )
Tagged with:
Feb 23

Got a lot done today:

The location bar now works fully, the user can type in an address, and if it is invalid, an error message is displayed, and it redirects back to the current directory. A simple thing in theory, but by adding this location bar, I have had to change many methods, and their paramaters, and how they work :/. But it all works good now!!

Also, reworked the file system tree, so that it is built on demand. Complete nightmare. Absolute nightmare. But the speed improvements were well worth it, the program loads in about 1.5 seconds now, where it was taking a minimum of 71 seconds previously just to index the whole system, and that was only if it had been run before and some of the results were cached/in memory. The tree building takes .7 of a second now. I used System.Time and diffClockTimes to work out the time differences.

Unfortunately I didn’t get to do my Haddock comments, because making the tree dynamic took a lot longer than expected. How it works is: I take the tree from the tree store when an onRowExpanded signal is generated by the user, and use my getPathAtActivation function, which I described previously to generate the file path that is to be expanded, I then index the folders in that directory, and add them to the store.

Old:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
dirContents :: FilePath -> IO ( Tree String )                        
dirContents name = do
        --Get folder title for node.
        let (_, title) = splitFileName name
        --Get the directory contents.
        --If error occurs, print error message.
        conts <- getDirectoryContents name `E.catch` (\ e  -> ( errorWindow $ show e ) >> return ( [] ) )
        --Filter hidden locations.
        let shownContents = ( filter ( not . isPrefixOf "." ) conts)
        --Filter directoryies.
        dirs <- filterM doesDirectoryExist ( map ( name </> ) shownContents )
        --Filter links from directory.
        nonLinks <- filterLinks dirs
        --Create shared variable for threads.
        m <- newEmptyMVar
        --For each accessibe directory
        subNode <- forM ( sort nonLinks ) $ \dir -> do
                        --Fork thread to recursively add sub-nodes to this node.
                        forkIO $ ( do 
                                res <- dirContents dir
                                --Put directory contents in m.
                                putMVar m ( res ) ) `E.catch` (\e -> do 
                                                                let (_, sname ) = splitFileName dir
                                                                putMVar m ( Node { rootLabel = ( "/" ++ sname ),
                                                                                   subForest = [] } )  >> return ( ) )
                        --When thread exits, take sub-nodes.
                        takeMVar m
        return ( Node { rootLabel = ( "/" ++ title ),
                        subForest = subNode } )

New:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
addSubTree :: G.TreePath -> MVTree.TreeStore String -> IO ( )
addSubTree path store = do
        newtree <- getPathAtActivation path store >>= dirContentsTwo
        forM ( reverse ( subForest newtree ) ) $ \sub -> TS.treeStoreInsertTree store path 0 sub
        ( TS.treeStoreRemove store ( path ++ [length ( subForest newtree)] ) ) >> return ( )
 
subDirSize :: FilePath -> IO Int
subDirSize dir = do
        dirs <- ( getDirectoryContents dir >>= ( \c -> filterM doesDirectoryExist ( map ( dir </> ) ( filter ( not . isPrefixOf "." ) c ) ) ) ) >>= filterLinks
        return ( length dirs )
 
dirContentsTwo :: FilePath -> IO ( Tree String )
dirContentsTwo path = do
        let (_, title) = splitFileName path
        --Get valid folders
        dirs <- ( getDirectoryContents path >>= ( \c -> filterM doesDirectoryExist ( map ( path </> ) ( filter ( not . isPrefixOf "." ) c ) ) ) ) >>= filterLinks
        --create sorted nodes & check if they contain folders
        nodes <- forM ( sort dirs ) $ \d -> do
                                        let (_ , dname) = splitFileName d
                                        size <- subDirSize d
                                        case size of
                                                0 -> return ( Node { rootLabel  = "/" ++ dname,
                                                                     subForest = [] } )
                                                _ -> return ( Node { rootLabel = "/" ++ dname,
                                                                subForest = [Node { rootLabel = "",
                                                                                    subForest = [] } ] } )
        return ( Node { rootLabel = ( "/" ++ title ),
                        subForest = nodes } )

Actually, I just spotted an error: if you keep clicking expand/collapse repeatedly, it adds the list over and over again! :( MORE FUN!

Tagged with:
Feb 22

I added support for assigning appropriate Icons to the different filetypes, based on the extension, using File Icon Vs.2 icon pack by Jordan Michael. Conveniently, these were named according to their extension and just needed to be resized to 16×16 pixels to fit in with the rest of the browser. If the filetype is “” – directory, or some text files – or an error is thrown while opening the appropriate icon, i.e. unknown file type, a default file/folder icon is used.

1
2
3
4
5
6
7
8
pixbuf <- case ext of
                "" -> do
                        fileE <- doesDirectoryExist file
                        case fileE of
                                True -> ( pixbufNewFromFile =<< iconpath ( IconDetails "Folder" ) )
                                False -> ( pixbufNewFromFile =<< iconpath ( IconDetails "File" ) )
                name -> ( pixbufNewFromFile =<< iconpath ( IconDetails ( tail name ) ) )
                                        `E.catch` (_ -> pixbufNewFromFile =<< iconpath( IconDetails "File" ) )

“File” and “Folder” are the names of the default file/folder icons.

I also added a location bar to the window. So instead of having the border title set to the current location, the text in this entryBox will be set to the current location, and the user can type in the location of a folder here if they want.

I added activation to the search box functionality, now the user can either press return after typing in their search term, or press the search button like before. I also moved the search box up to the same hbox that the location bar is in, but I think that it looks quite ugly to be honest, so I’m going to have to try to find a way to improve it. Maybe do something like nautilus, where by a user hits a Search button and the location bar becomes the entry for the search term.

I removed the hover-expand functionality from the system tree, as it is quite annoying, and I can’t find a way to set a delay on it.

The search function is still quite sluggish, which is quite annoying, but I can’t figure out a way to speed it up anymore.

Here’s a screenshot of how it looks now, ignore the theme, it doesn’t look to good on the program:

Screenshot

Tomorrow: Comment all code, and generate documentation using Haddock

M

Tagged with:
Feb 17

I decided to try my file manager on Ubuntu, to see how it functioned on a Debian OS. In theory it should have worked the same, and I haven’t had a chance to try it out fully yet, so I can’t comment on that just yet, but installing GHC and Gtk2HS on Ubuntu is slightly more complex than on Fedora, where there was a package that took care of everything.

GHC

Firstly install GHC:

$ sudo apt-get install ghc

Note: This package doesn’t install all the GHC libraries, just core functionality, stuff like Text.Regex.Posix has to be installed using Cabal, detailed later.

GTK2HS

Next, get the source for Gtk2HS, I’m installing Gtk2HS 0.9.13. Download that to your home directory and unpack it.

Open a terminal and change to the where you unpacked it, for me that was:

$ cd ~/gtk2hs-0.9.13/

Now install some dependencies for the Gtk2HS package:

$ sudo apt-get install build-essential libghc6-mtl-dev libglib2.0-dev build-dep libghc6-gtk-dev

Now run the configuration script ( with hcflags set to O2 for optimized code in the library – slower build )

$ ./configure --with-hcflags=O2

Next, the package needs to be built and installed:

$ sudo make
$ sudo make install

Thats it! Gtk2HS is installed.

To be sure that it is installed properly, navigate to the demo folder inside the Gtk2HS source folder, for me it was:

$ cd ~/gtk2hs-0.9.13/demo/hello/

Now compile the file that’s in there, and run it:

$ make
$ ./helloworld

If you get any errors, you haven’t installed it properly. If not, and all is well, you should see something like this:

Hello World

Cabal

My file manager uses the Text.Regex.Posix library, which is included in the Fedora GHC package, but not in the Ubuntu one. So I needed to install it using Cabal install.

Firstly get the tar.gz file from this directory: cabal install package, and like the Gtk2HS package unpack it and navigate in a terminal to the folder, for me it was:

$ cd ~/cabal-install-x
where x is the version number

Next, install some dependencies:

$ sudo apt-get install libghc6-network-dev libghc6-parsec-dev

Now run the bootstrap.sh script as this installs the neccessary packages:

Note: You may need to make it executable:

$ sudo chmod +x bootstrap.sh
$ ./bootstrap.sh

That should install everything, and you will need to add the path that appears to your $PATH variable.

Now, the Text.Regex.Posix package is “regex-posix” for cabal install, so to install it type:

$ cabal install regex-posix

And that will do the business for you!

M

Tagged with:
preload preload preload