5.2 File Selection

Files and directories (folders) are essential to almost every computer program and Gtk contains many components to facilitate their handling. User selection of files and folders in Gtk2Hs is implemented through the FileChooser interface. Basically there are four modes, as expressed in the FileChooserAction type. Its constructors are:

The FileChooser interface has attributes, methods and signals, but is not itself a widget. There are three widgets that use the interface in different manners, the FileChooserWidget , FileChooserButton and FileChooserDialog . Which widget you use may also restrict which FileChooserActionType is allowed. As you'll see in the examples below, a widget for saving a file or selecting a directory can also contain a button which lets the user create a directory. Therefore the FileActionCreateFolder constructor will probably never be used in any of your programs.

It is important to note that, while the widgets do not save or open files by themselves, the creation of directories (by the user) is actually implemented through the widgets.

Our first example will use a FileChooserWidget , which can be in Open or Save mode.

fileChooserWidgetNew :: FileChooserAction -> IO FileChooserWidget

We use FileChooserActionOpen here, and when the user definitely chooses a file by doubleclicking it or pressing the Enter key, the onFileActived signal is emitted. Then we use:

fileChooserGetFilename :: FileChooserClass self => self -> IO (Maybe FilePath)

From the filepath, the program can then open the file, or possibly do something else. The format of the filepath may depend on the platform and is determined by the G_FILENAME_ENCODING environment variable. There are also functions in FileChooser for URI (Uniform Resource Identifier) formats, but those are not discussed here.

You can determine whether the user can select multiple files or not with:

fileChooserSetselectMultiple :: FileChooserClass self => self -> Bool -> IO ()

and, with the FileChooserWidget , you can easily add a check button to let the user determine this. Placing of such a widget is done in a standard way with:

fileChooserSetExtraWidget :: (FileChooserClass self, WidgetClass extraWidget)
=> self -> extraWidget -> IO ()

Another feature is the use of filters to show only files of a certain type, either by specifying a MIME type, a pattern or a custom format. File filters are documented in Graphics.UI.Gtk.Selectors.FileFilter.

The following code snippet, from the example below, illustrates filters. The last line just adds the filter to the file chooser widget and, as with the extra widget, visual placing is done automatically.

   hsfilt <- fileFilterNew
   fileFilterAddPattern hsfilt "*.hs"
   fileFilterSetName hsfilt "Haskell Source"   
   fileChooserAddFilter fch hsfilt

You can also add a so-called preview widget with:

fileChooserSetPreviewWidget :: (FileChooserClass self, WidgetClass
previewWidget) => self -> previewWidget -> IO ()

In the example this is used to preview graphics files. The example uses an Image widget (documented in Graphics.UI.Gtk.Display.Image) like used before in Chapter 4.1. There we used imageNewFromFile to add graphics to a button; here we construct an empty Image widget.

To update it regularly, we have the onUpdatePreview signal, which is emitted whenever the user changes the file selection by moving the mouse or accelerator keys. Therefore this signal is more general than the name suggests, but here it is actually used for viewing. The code snippet is:

   onUpdatePreview fch $ 
        do file <- fileChooserGetPreviewFilename fch
           case file of
                Nothing -> putStrLn "No File Selected"
                Just fpath -> imageSetFromFile img fpath

There are functions and attributes to control the display, for example, what happens when a file is selected which is not a graphics file, but they are not really needed. In the test of the code below, non graphics files were just ignored, or indicated by a standard icon. This is how it all looked:

File Selection examples

Note that the user can also add and delete bookmarks, and FileChooser has functions to manage this as well. But this feature is not treated in the FileChooserWidget example, which has the following source code:

import Graphics.UI.Gtk

main :: IO ()
main = do
   initGUI
   window <- windowNew
   set window [windowTitle := "File Chooser Widget", 
               windowDefaultWidth := 500,
               windowDefaultHeight := 400 ]

   fch <- fileChooserWidgetNew FileChooserActionOpen
   containerAdd window fch 

   selopt <- checkButtonNewWithLabel "Multiple File Selection"
   fileChooserSetExtraWidget fch selopt

   hsfilt <- fileFilterNew
   fileFilterAddPattern hsfilt "*.hs"
   fileFilterSetName hsfilt "Haskell Source"   
   fileChooserAddFilter fch hsfilt

   nofilt <- fileFilterNew
   fileFilterAddPattern nofilt "*.*"
   fileFilterSetName nofilt "All Files"
   fileChooserAddFilter fch nofilt

   img <- imageNew
   fileChooserSetPreviewWidget fch img


   onUpdatePreview fch $ 
        do file <- fileChooserGetPreviewFilename fch
           case file of
                Nothing -> putStrLn "No File Selected"
                Just fpath -> imageSetFromFile img fpath

                           
   onFileActivated fch $ 
        do dir <- fileChooserGetCurrentFolder fch
           case dir of 
                Just dpath -> putStrLn 
                               ("The current directory is: " ++ dpath)
                Nothing -> putStrLn "Nothing" 
           mul <- fileChooserGetSelectMultiple fch 
           if mul 
              then do
                fls <- fileChooserGetFilenames fch
                putStrLn 
                  ("You selected " ++ (show (length fls)) ++ "files:")
                sequence_ (map putStrLn fls)
              else do
                file <- fileChooserGetFilename fch
                case file of
                     Just fpath -> putStrLn ("You selected: " ++ fpath)
                     Nothing -> putStrLn "Nothing"

   onToggled selopt $ do state <- toggleButtonGetActive selopt
                         fileChooserSetSelectMultiple fch state

   widgetShowAll window
   onDestroy window mainQuit
   mainGUI

Note: With Gtk2Hs 0.9-12 and GHC 6.1 on FC 6, multiple file selection worked visually (Ctrl and Shift keys indicated multiples as expected), but the list of file paths only contained the path of the last file.

The second way to use the FileChooser interface is through a FileChooserButton .

fileChooserButtonNew :: String FileChooserAction -> String ->
IO FileChooserButton

The String parameter is the name of the browse dialog that pops up when the user select the 'other...' option after pressing the button. In the example below we constructed a file chooser button with FileChooserActionSelectFolder. This is how the button looked after selecting directory "Test".

File Selection examples

This is how the dialog looked:

File Selection examples

As you can see, there is a "Create Folder" button at the top right hand side of the dialog window and this can be used to actually create a new directory. This is what happened when I tried to create an existing folder:

File Selection examples

Creating or overwriting an existing directory makes no sense and would be dangerous, so Gtk2Hs automatically takes care of it and notifies the user. When the user selects an existing directory the onCurrentFolderChanged signal is emitted and the program can take the appropriate action. Creating a directory automatically selects it, so in that case onCurrentFolderChanged can be used as well. Here is the example code:

import Graphics.UI.Gtk

main :: IO ()
main = do
     initGUI
     window <- windowNew
     set window [windowTitle := "File Chooser Button",
windowDefaultWidth := 250, windowDefaultHeight := 75 ]
     fchd <- fileChooserButtonNew "Select Folder"
FileChooserActionSelectFolder
     containerAdd window fchd

     onCurrentFolderChanged fchd $
          do dir <- fileChooserGetCurrentFolder fchd   
             case dir of
                  Nothing -> putStrLn "Nothing"
                  Just dpath -> putStrLn ("You selected:\n" ++ dpath)

     widgetShowAll window
     onDestroy window mainQuit
     mainGUI

The third way to use the FileChooser interface is through a FileChooserDialog . It can be constructed in the open or save mode, and is usually applied from a menu or a toolbar.

FileChooserDialog implements both FileChooser and Dialog . Recall from Chapter 4.5 that a dialog is a composite widget with buttons, usually implemented with dialogRun, which produces responses of type ResponseId . A FileChooserDialog is constructed with:

fileChooserDialogNew ::
Maybe String                 -- title of the dialog or default
-> Maybe Window              -- parent window of the dialog or nothing
-> FileChooserAction         -- open or save mode
-> [(String, ResponseId)]    -- list of buttons and their response codes
-> IO FileChooserDialog

All you have to do is specify the names of the buttons and their responses in the fourth argument, and they will automatically be implemented.

The example takes FileChooserActionSave and the dialog has three buttons. This is what it looks like:

File Selection examples

As you can see there is a button at the top right to create a folder. As in the example before, trying to create a folder which already exists results in an error message. Overwriting a file, however, makes sense and is allowed by default. You can make the user confirm any overwite of a file with:

fileChooserSetDoOverwriteconfirmation :: FileChooserClass self
=> self -> Bool -> IO ()

As mentioned earlier, no actual save or overwrite is performed by the FileChooserDialog widget; the application program just gets the appropriate file path.

This is the code of the third example:

import  Graphics.UI.Gtk 
 
main :: IO ()
main = do
     initGUI
     fchdal <- fileChooserDialogNew (Just "Save As...Dialog") Nothing
                                     FileChooserActionSave
                                     [("Cancel", ResponseCancel),
                                      ("Save", ResponseAccept),
                                      ("Backup", ResponseUser 100)]
 
     fileChooserSetDoOverwriteConfirmation fchdal True
     widgetShow fchdal
     response <- dialogRun fchdal
     case response of
          ResponseCancel -> putStrLn "You cancelled..."
          ResponseAccept -> do nwf <- fileChooserGetFilename fchdal
                               case nwf of
                                    Nothing -> putStrLn "Nothing"
                                    Just path -> putStrLn ("New file path is:\n" ++ path)
          ResponseUser 100 -> putStrLn "You pressed the backup button"
          ResponseDeleteEvent -> putStrLn "You closed the dialog window..."

     widgetDestroy fchdal
     onDestroy fchdal mainQuit
     mainGUI

Note: When testing with Gtk2Hs 0.9-12 and GHC 6.1 on FC6, pressing the Enter key to save a file had no effect. When a file was chosen which already existed, pressing the Save button had no effect the first time, but pressing again produced the confirmation dialog. My own thought is that this might have something to do with the onConfirmOverwrite signal and its second argument of type IO FileChooserConfirmation. Its use is unclear to me, and the fault may be with my code.