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.