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:
FileChooserActionOpen
used to let the user open a
fileFileChooserActionSave
used to let the user save a
fileFileChooserActionSelectFolder
used to let a user
select a directoryFileChooserActionCreateFolder
used to let a user
create a directoryThe
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:
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".
This is how the dialog looked:
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:
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:
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.