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:
FileChooserActionOpen usado para que el usuario abra un ficheroFileChooserActionSave usado para que el usuario guarde un ficheroFileChooserActionSelectFolder usado para que el usuario seleccione un directorioFileChooserActionCreateFolder usado para que el usuario cree un directorioEl 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:
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í.
Así es como se vería la ventana de diálogo:
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:
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:
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.