5.2 Selección de fichero

Los ficheros y los directorios (carpetas) son esenciales en cualquier programa de ordenador y Gtk contiene diversos componentes para facilitar su manejo. La selección de ficheros y directorios en Gtk2Hs se implementa a través del interfaz FileChooser. Basicamente hay cuatro modos, como se indica en el tipo FileChooserAction. Sus constructores son:

El interfaz FileChooser tiene atributos, métodos y señales, pero no es propiamente un widget. Hay tres widgets que usan el interfaz de modo diferente, FileChooserWidget , FileChooserButton y FileChooserDialog . El widget a usar está restingido por la FileChooserActionType permitida. Como verás en los ejemplos siguientes, el widget para guardar un fichero o para seleccionar un directorio puede contener también un botón que permita al usuario crear un directorio. Además, el constructor FileActionCreateFolder probablemente nuncá será usado en ninguno de tus programas.

Es importante indicar que, a pesar de que los widgets no guardan ni abren ficheros por sí mismos, le creación de los directorios (por el usuario) se implementa a través de widgets.

Nuestro primer ejemplo usará FileChooserWidget , que puede emplearse en el modo Abrir y Salvar.

fileChooserWidgetNew :: FileChooserAction -> IO FileChooserWidget

Aquí usamos FileChooserActionOpen, y cuando el usuario elige definitivamente un fichero, ya sea haciendo doble clic en él o pulsando la tecla Enter, la señal onFileActived se emite. Usamos:

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

Desde la ubicación del fichero, el programa puede abrir el fichero, o posiblemente hacer otra cosa. El formato del filepath puede depender de la plataforma y está determinado por la variable de entorno G_FILENAME_ENCODING. Hay también funciones en FileChooser para formatos URI (Uniform Resource Identifier), pero no las vamos a ver aquí.

Puedes permitir al usuario seleccionar múltiples ficheros con:

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

y, con el FileChooserWidget , puedes añadir fácilmente un botón check para dejar al usuario determinarlo. La colocación de un widget de este tipo se hace de modo estándar con:

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

Otra utilidad es el uso de filtros para mostrar sólo ficheros de un tipo, ya sea especificando un tipo MIME, un pattern (plantilla) o un formato a medida. Los filtros de ficheros se documentan en Graphics.UI.Gtk.Selectors.FileFilter.

El siguiente trozo de código, parte del ejemplo siguiente, muestra los filtros. La última línea simplemente añade el filtro al widget selector de fichero y, como ocurre con el widget extra, el posicionamiento visual se hace automáticamente.

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

Puedes también añadir un widget "preview" (previsualización) con:

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

En el ejemplo se usa para previsualizar ficheros gráficos. El ejemplo usa un widget Image (documentado en Graphics.UI.Gtk.Display.Image) como los usados antes en el Capítulo 4.1. Allí usamos imageNewFromFile para añadir gráficos a un botón; aquí construímos un widget Image vacío.

Para actualizarlo cuando haya cambios, tenemos una señal onUpdatePreview, que se emite cada vez que el usuario cambia la selección de fichero moviendo el ratón o con las teclas de aceleración. Esta señal es más general que lo que su nombre sugiere, pero aquí se usa sólo para previsualizar. El código es el siguiente:

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

Hay funciones y atributos para controlar lo que se muestra, por ejemplo lo que sucede cuando el fichero seleccionado no es un fichero gráfico, pero no son estrictamente necesarios. En el resto del código los ficheros no gráficos se ignoran o se muestra un icono estándar. Así es como aparecen:

File Selection examples

Fíjate en que el usuario también puede añadir y borrar bookmarks, y FileChooser tiene funciones para gestionar esto también. Sin embargo, esta característica no se trata en el ejemplo FileChooserWidget , que tiene el siguiente código fuente:

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

Nota: Con Gtk2Hs 0.9-12 y GHC 6.1 en Fedora Core 6, la selección múltiple de ficheros funciona visualmente (las teclas Ctrl y Shift funcionan como el usuario supone), pero la lista de direcciones de fichero sólo contiene la dirección del último fichero seleccionado.

El segundo modo de usar el interface FileChooser es a través de FileChooserButton .

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

El parámetro tipo String es el nombre de la ventana de diálogo que salta cuando el usuario selecciona la opción 'other...' después de pulsar el botón. En el ejemplo hemos construido un botón de selección de fichero con FileChooserActionSelectFolder. Tras seleccionar el directorio "Test", se vería así.

File Selection examples

Así es como se vería la ventana de diálogo:

File Selection examples

Como puedes ver, hay un botón "Create Folder" (Crea Carpeta) en la parte superior derecha de la ventana de diálogo. La puedes usar para crear un directorio. Esto es lo que sucede si tratamos de crear una carpeta existente:

File Selection examples

Crear o sobrescribir un directorio existente no tiene sentido y puede ser peligroso. Así que Gtk2Hs automáticamente lo percibe y lo notifica al usuario. Cuando el usuario selecciona un directorio existente, la señal onCurrentFolderChanged se emite y el programa puede tomar la acción apropiada. Al crear un directorio se selecciona automáticamente, así que, en ese caso, la señal onCurrentFolderChanged también puede ser usada. Aquí está el código del ejemplo:

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 tercera manera de usar el interfaz FileChooser es a través de FileChooserDialog . Puede ser construido en modo abrir o salvar, y normalmente se aplica desde un menú o una barra de herramientas.

FileChooserDialog implementa tanto FileChooser como Dialog . Recuerda del Capítulo 4.5 que un "diálogo" es un widget compuesto con botones, normalmente implementados con dialogRun, que produce respuestas del tipo ResponseId . Un FileChooserDialog se construye con:

fileChooserDialogNew ::
Maybe String                 -- título del diálogo o "por defecto"
-> Maybe Window              -- Ventana "padre" del diálogo o nada
-> FileChooserAction         -- modo abrir o salvar
-> [(String, ResponseId)]    -- lista de botones y sus códigos de respuesta
-> IO FileChooserDialog

Todo lo que tienes que hacer es indicar los nombres de los botones y sus respuestas en el cuarto argumento, y serán automáticamente implementados.

El ejemplo usa FileChooserActionSave y la ventana de diálogo tiene tres botones. Así es como queda:

File Selection examples

Como puedes ver aquí hay un botón en la parte superior derecha para crear una carpeta. Como en el ejemplo anterior, intentar crear una carpeta ya existente genera un mensaje de error. Sobreescribir un fichero, sin embargo, tiene sentido y es admitido por defecto. Puedes ahcer que el usuario confirme la sobreescritura de ficheros con:

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

Como ya mencioné, no se realizan escrituras o sobrescrituras de ficheros con el widget FileChooserDialog; El programa simplemente obtiene el path del fichero.

Este es el código del tercer ejemplo:

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

Nota: Al probarlo con Gtk2Hs 0.9-12 y GHC 6.1 en Fedora Core 6, pulsar la tecla "Enter" para guardar el fichero no tiene ningún efecto. Cuando se elige un fichero existente, pulsar la tecla "Save" no tiene efecto la primera vez, pero si se pulsa de nuevo provoca la aparición de la ventana de confirmación. Mi opinión es que esto tiene algo que ver con la señal onConfirmOverwrite y su segundo argumento de tipo IO FileChooserConfirmation. No entiendo bien su uso, y quizá el error provenga de mi código.