Tutoriel Gtk2Hs

5.2 Sélection de fichiers

Les fichiers et les dossiers sont essentiels dans tous les programmes et Gtk possède beaucoup de composants pour faciliter leur manipulation. La sélection de fichiers et de dossiers par l’utilisateur est implémentée au travers de l’interface FileChooser. Il y a 4 modes de base pour le type FileChooserAction. Ses constructeurs sont:

  • FileChooserActionOpen Utilisé pour permettre à l’utilisateur d’ouvrir un fichier.
  • FileChooserActionSave Utilisé pour permettre à l’utilisateur de sauver un fichier. file
  • FileChooserActionSelectFolder Utilisé pour permettre à l’utilisateur de sélectionner un dossier.
  • FileChooserActionCreateFolder Utilisé pour permettre à l’utilisateur de créer un dossier.

L’interface FileChooser a des attributs, des méthodes et des signaux mais ce n’est pas à proprement parler un widget. Il y a trois widgets qui utilisent l’interface de différentes manières: FileChooserWidget , FileChooserButton et FileChooserDialog. Comme vous le verrez dans l’exemple plus loin, un widget pour sauver un fichier ou sélectionner un dossier peut aussi contenir un bouton pour créer un dossier. Par conséquent, le constructeur FileActionCreateFolder ne sera probablement jamais utilisé dans vos programmes.

Il est important de noter que, bien que les widgets n’ouvrent pas et ne sauvent pas eux même les fichiers, la création des dossiers, au contraire, se fait par les widgets.

Notre premier exemple utilisera FileChooserWidget qui peut être dans le mode Ouvrir ou Sauver.

fileChooserWidgetNew :: FileChooserAction -> IO FileChooserWidget

On utilise FileChooserActionOpen ici, et quand l’utilisateur choisit un fichier en double-cliquant dessus ou en appuyant sur la touche Entrée, le signal onFileActived est émis. On utilise alors:

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

A partir du chemin (FilePath), le programme peut alors ouvrir le fichier. Le format du chemin varie selon la plateforme et est déterminé par la variable d’environnement G_FILENAME_ENCODING.

Vous pouvez paramétrer si l’utilisateur peut sélectionner plusieurs fichiers ou non avec :

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

et avec FileChooserWidget, vous pouvez facilement ajouter une case à cocher pour laisser l’utilisateur choisir. Placer un widget de ce type se fait avec:

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

Une autre fonctionnalité est l’utilisation de filtres pour afficher seulement les fichiers d’un certains type, soit en spécifiant leur type MIME, soit en spécifiant un motif, soit en spécifiant un format. Les filtres sont documentés dans Graphics.UI.Gtk.Selectors.FileFilter.

Le morceau de code qui suit montre l’utilisation des filtres. La dernière ligne ajoute les filtres dans le widget sélecteur de fichiers et, tout comme les widget complémentaires, le placement se fait automatiquement.

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

Vous pouvez également ajouter un widget de prévisualisation avec:

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

Dans l’exemple, ce widget est utilisé pour afficher les fichiers images. L’exemple utilise un widget Image (documenté dans Graphics.UI.Gtk.Display.Image) comme utilisé auparavant dans le chapitre 4.1. Nous avions utilisé alors imageNewFromFile pour ajouter des images sur un bouton; ici on construit un widget Image vide. Pour le mettre à jour, on utilise le signal onUpdatePreview qui est émit à chaque fois que l’utilisateur change sa sélection de fichier en bougeant la souris ou en appuyant sur les touches de raccourcis. Ce signal est plus général qu’il n’en a l’air, mais ici il n’est utilisé que pour la prévisualisation. Le bout de code est :

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

Il y a des fonctions et des attributs pour contrôler l’affichage; par exemple, ce qui se passe quand on sélectionne un fichier qui n’est pas un fichier graphique, mais elles ne sont pas strictement nécessaires. Dans l’exemple suivant, les fichiers non-graphiques sont simplement ignorés ou indiqués par une icône standard. Voila a quoi tout cela ressemble:

Gtk Gtk2Hs File Selection examples

Notez que l’utilisateur peut aussi ajouter et supprimer des marque-pages et que FileChooser possède également des fonctions pour les gérer. Mais cette fonctionnalité n’est pas abordée dans l’exemple qui suit:

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

La seconde façon d’utiliser l’interface FileChooser est un bouton de sélection FileChooserButton.

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

Le paramètre String est le nom du dialogue qui s’affiche quand l’utilisateur sélectionne l’option après avoir pressé sur le bouton. Dans l’exemple ci-dessous, on a construit un bouton sélecteur de fichiers avec FileChooserActionSelectFolder. Voici a quoi ressemble le bouton après avoir sélectionné le répertoire .

Gtk Gtk2Hs File Selection examples

Voici a quoi ressemble le dialogue:

Gtk Gtk2Hs File Selection examples

Comme on peut le voir, il y a un bouton en haut à droite de la fenêtre de dialogue et il peut être utilisé pour créer un nouveau dossier. Voici ce qui arrive quand on essaie de créer un dossier déjà existant:

Gtk Gtk2Hs File Selection examples

Créer ou recréer un dossier déjà existant peut causer des problèmes, c’est pour cette raison que Gtk2Hs se charge d’avertir automatiquement l’utilisateur. Quand l’utilisateur sélectionne un répertoire existant, le signal onCurrentFolderChanged est émit et le programme peut lancer l’action appropriée. Créer un dossier le sélectionne automatiquement, on peut donc utiliser onCurrentFolderChanged pour travailler sur ce dossier. Voici un petit exemple:

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

La troisième manière d’utiliser une interface FileChooser se fait avec un FileChooserDialog. Elle peut être construite en mode ouvrir ou sauver et est habituellement lancée depuis un menu ou une barre d’outils.

FileChooserDialog implémente à la fois FileChooser et Dialog. Rappelons (chapitre 4.5) qu’un dialogue est un widget composite avec des boutons généralement implémenté avec dialogRun qui produit des sorties de type ResponseId. FileChooserDialog est construit avec:

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

Tout ce que l’on a faire est de spécifier les noms des boutons et leurs sorties (ResponseId) dans le quatrième argument et ils seront implémentés automatiquement.

L’exemple lance un widget FileChooserActionSave et ce dialogue possède trois boutons. Voici a quoi il ressemble:

Gtk Gtk2Hs File Selection examples

Comme vous pouvez le voir, il y a un bouton en haut à droite pour créer un dossier. Comme dans l’exemple précédent, tenter de créer un dossier déjà existant entraîne un message d’erreur. Toutefois, on peut réécrire sur un fichier (autorisé par défaut). Vous pouvez faire confirmer la réécriture d’un fichier par l’utilisateur avec:

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

Comme expliqué auparavant, ni enregistrement, ni écriture n’est faite par le widget FileChooserDialog; le programme obtient seulement le chemin d’accès

Voici le code du troisième exemple:

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