From: Axel S. <A....@ke...> - 2008-01-11 20:04:40
|
Tue Jan 1 08:57:58 PST 2008 hth...@zo... * Tutorial-Port Spanish Chapters 5, 6 and 7 (also corrected a previous typo) hunk ./docs/tutorial/Tutorial_Port/es-chap4-7.xhtml 1 -<!--<?xml version="1.0" encoding="UTF-8"?--> +<?xml version="1.0" encoding="UTF-8"?> addfile ./docs/tutorial/Tutorial_Port/es-chap5-1.xhtml hunk ./docs/tutorial/Tutorial_Port/es-chap5-1.xhtml 1 - +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="es" lang="es"><head> + [_$_] + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <title>Tutorial de Gtk2Hs: Calendario</title> + [_$_] + <link href="default.css" type="text/css" rel="stylesheet" /></head><body> + <div id="header"> + <h1>Tutorial de Gtk2Hs</h1> + <span class="nav-previous"><a href="es-chap4-7.xhtml">Previo</a></span> + <span class="nav-home"> + <a href="es-index.xhtml">Inicio</a> + </span> + <span class="nav-next"><a href="es-chap5-2.xhtml">Siguiente</a></span> + </div> + <h3>Calendario</h3> + <p>El widget Calendar es una buena manera de mostrar y recuperar + la información cronológica. Es un widget muy sencillo de crear y su + funcionamiento es muy simple. Debes usar:</p> + <pre class="codebox">calendarNew: IO Calendar +</pre> + <p>Por defecto se muestra el mes actual. Para recuperar la información de + un calendario emplea:</p> + <pre class="codebox">calendarGetDate :: CalendarClass self => self -> IO (Int, Int, Int) +</pre> + <p>donde la terna contendría la información (año, mes, día). (Enero es el 0). [_$_] + Los atributos relacionados son:</p> + <pre class="codebox">calendarYear :: CalendarClass self => Attr self Int +calendarMonth :: CalendarClass self => Attr self Int +calendarDay :: CalendarClass self => Attr self Int +</pre> + <p>El widget Calendar tiene algunas opciones que te permiten cambiar la apariencia y el modo de operación [_$_] + del widget. Para ello debes usar la función + <code>calendarSetDisplayOptions</code>. Para recuperar los valores usa: + <code>calendarGetDisplayOptions</code>.</p> + <pre class="codebox">calendarSetDisplayOptions :: CalendarClass self => self -> [CalendarDisplayOptions] -> IO () +calendarGetDisplayOptions :: CalendarClass self => self -> IO [CalendarDisplayOptions] +</pre> + <p> + <code>CalendarDisplayOptions</code> tiene los siguientes constructores:</p> + <ul> + <li>CalendarShowHeading: especifica el año y el mes a mostrar + por el calendario.</li> + <li>CalendarShowDayNames: indica que se debe poner una descripción de [_$_] + tres letras indicando el nombre del día de la semana (eg Lun,Mar, etc.).</li> + <li>CalendarNoMonthChange: señala que el usuario ni debe, ni puede, cambiar + el mes mostrado. Esto sirve en el caso de sólo necesites mostrar un mes [_$_] + concreto, como cuando creas 12 widgets mensuales para mostar un año completo.</li> + <li>CalendarShowWeekNumbers: esta opción indica que se debe mostrar el número de la semana [_$_] + en el lado izquierdo del calendario. (ej. Ene 1 = Semana 1,Dic 31 = Semana 52).</li> + <li>CalendarWeekStartMonday (fíjate en la nota de abajo): Esta opción indica que la semana + del calendario debe empezar en lunes en vez de en domingo (valor por defecto). Esto sólo + afecta al orden en que se muestran los días de izquierda a derecha.</li> + </ul> + <p>También puede obtenerse y cambiarse el valor de las opciones usando atributos + Booleanos mediante las funciones genéricas <code>get</code> y <code>set</code>.</p> + <p class="notebox">Nota: No hay atributo para CalendarWeekStartMonday y el uso de la función [_$_] + <code>calendarSetDisplay</code> origina un mensaje en tiempo de ejecución indicando que + el primer día de la semana se toma por defecto y se ignora GTK_CALENDAR_WEEK_START_MONDAY.</p> + <p>Por último, destacar que se pueden marcar algunos días del mes. Un día marcado [_$_] + se resalta en el calendario mostrado. Las siguientes funciones (sin atributos) sirven para manipular + los días marcados:</p> + <pre class="codebox">calendarMarkDay :: CalendarClass self => self -> Int -> IO Bool +calendarUnmarkDay :: CalendarClass self => self -> Int -> IO Bool +calendarClearMarks :: CalendarClass self => self -> IO () +</pre> + <p>El valor Booleano no se usa (siempre True). Las marcas se mantienen + en los cambios de mes y de año.</p> + <p>El widget Calendar puede generar señales que indican los cambios y la fecha [_$_] + seleccionada. Los nombres de esas señales son:</p> + <ul> + <li>onDaySelected</li> + <li>onDaySelectedDoubleClick</li> + </ul> + <p class="notebox">Nota: lo siguiente está mencionado en la documentación de la API, pero [_$_] + aparece implementado como onDaySelected. Mira el comentario en el código del ejemplo.</p> + <ul> + <li>onMonthChanged</li> + <li>onNextMonth</li> + <li>onNextYear</li> + <li>onPrevMonth</li> + <li>onPrevYear</li> + </ul> + <p>El siguiente ejemplo muestra el uso del widget Calendar:</p> + <p> + <img src="Images/GtkChap5-1.png" alt="GtkChap5-1.png" id="graphics1" /> + </p> + <pre class="codebox">import Graphics.UI.Gtk + +main :: IO () +main= do + initGUI + window <- windowNew + set window [windowTitle := "Calendar", + windowDefaultWidth:= 200, + windowDefaultHeight:= 100] + mainbox <- vBoxNew True 0 + containerAdd window mainbox + + hbox1 <- hBoxNew True 0 + boxPackStart mainbox hbox1 PackGrow 0 + + cal <-calendarNew + boxPackStart hbox1 cal PackGrow 0 [_$_] + + vbox1 <- vBoxNew True 0 + frame1 <- frameNew + set frame1 [frameLabel := "Display Options", + containerBorderWidth := 10, + frameLabelYAlign := 0.5, [_$_] + frameLabelXAlign := 0.5, + containerChild := vbox1 ] + boxPackStart hbox1 frame1 PackGrow 0 + headingopt <- addDisplayOpt vbox1 "Show Heading" + daynameopt <- addDisplayOpt vbox1 "Show Day Names" + monchngopt <- addDisplayOpt vbox1 "No Month Change" + weeknumopt <- addDisplayOpt vbox1 "Show Week Numbers" + + set headingopt [toggleButtonActive := True] + set daynameopt [toggleButtonActive := True] + + reslabel <- labelNew Nothing + showMess cal reslabel "Nothing Done Yet" + frame2 <- frameNew + set frame2 [frameLabel := "Last Action:", + containerBorderWidth := 10, [_$_] + containerChild := reslabel] + boxPackStart mainbox frame2 PackGrow 0 [_$_] + + mySetOnToggled headingopt cal calendarShowHeading + mySetOnToggled daynameopt cal calendarShowDayNames + mySetOnToggled monchngopt cal calendarNoMonthChange + mySetOnToggled weeknumopt cal calendarShowWeekNumbers + + onDaySelected cal (showMess cal reslabel "Day Selected") + onDaySelectedDoubleClick cal [_$_] + (showMess cal reslabel "Double Click Day Selected") + + widgetShowAll window + onDestroy window mainQuit + mainGUI + + +addDisplayOpt :: VBox -> String -> IO CheckButton +addDisplayOpt box lbl = do + cb <- checkButtonNewWithLabel lbl + boxPackStart box cb PackGrow 5 + return cb + +mySetOnToggled :: CheckButton -> Calendar -> [_$_] + Attr Calendar Bool -> [_$_] + IO (ConnectId CheckButton) +mySetOnToggled cb cl att = onToggled cb $ do + cbstate <- get cb toggleButtonActive + set cl [att := cbstate] + +showMess :: Calendar -> Label -> String -> IO () +showMess cal lbl str = do [_$_] + (year, month, day) <- calendarGetDate cal + labelSetText lbl $ str ++ "\n" ++ "Date = " ++ + (show year) ++ "//" ++ [_$_] + (myshow (month +1)) -- month is 0 to 11 + ++ "//" ++ (myshow day) [_$_] + where myshow n | n <= 9 = '0':(show n) + | otherwise = show n + +{- Comentado para hacer una comprobación específica de la plataforma: +Estas señales parecen implementadas como onDaySelected. +La plataforma es: Gtk2Hs 0.9.12 en Fedora Core 6 + + onMonthChanged cal (showMess cal reslabel "Month Changed") + onNextMonth cal (showMess cal reslabel "Next Month Selected") + onNextYear cal (showMess cal reslabel "Next Year Selected") + onPrevMonth cal (showMess cal reslabel "Previous Month +Selected") + onPrevYear cal (showMess cal reslabel "Previous Year +Selected") +-} +</pre> + <div id="footer"> + <span class="nav-previous"><a href="es-chap4-7.xhtml">Previo</a><br />Botones aumentar/disminuir</span> + <span class="nav-home"> + <a href="es-index.xhtml">Inicio</a> + </span> + <span class="nav-next"><a href="es-chap5-2.xhtml">Siguiente</a><br />Selección de fichero</span> + </div> + [_$_] +</body></html> addfile ./docs/tutorial/Tutorial_Port/es-chap5-2.xhtml hunk ./docs/tutorial/Tutorial_Port/es-chap5-2.xhtml 1 - +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="es"><head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <title>Tutorial de Gtk2Hs: Selección de fichero</title> + [_$_] + <link href="default.css" type="text/css" rel="stylesheet" /></head><body> + <div id="header"> + <h1>Tutorial de Gtk2Hs</h1> + <span class="nav-previous"> + <a href="es-chap5-1.xhtml">Previo</a> + </span> + <span class="nav-home"> + <a href="es-index.xhtml">Inicio</a> + </span> + <span class="nav-next"> + <a href="es-chap5-3.xhtml">Siguiente</a> + </span> + </div> + <h2>5.2 Selección de fichero</h2> + <p>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 <code>FileChooser</code>. Basicamente hay cuatro + modos, como se indica en el tipo <code>FileChooserAction</code>. Sus constructores son:</p> + <ul> + <li> + <code>FileChooserActionOpen</code> usado para que el usuario abra un fichero</li> + <li> + <code>FileChooserActionSave</code> usado para que el usuario guarde un fichero</li> + <li> + <code>FileChooserActionSelectFolder</code> usado para que el usuario seleccione un directorio</li> + <li> + <code>FileChooserActionCreateFolder</code> usado para que el usuario cree un directorio</li> + </ul> + <p>El interfaz [_$_] + <code>FileChooser</code> tiene atributos, métodos y señales, pero no es propiamente un widget. Hay tres + widgets que usan el interfaz de modo diferente, [_$_] + <code>FileChooserWidget</code> , [_$_] + <code>FileChooserButton</code> y [_$_] + <code>FileChooserDialog</code> . El widget a usar está restingido por la + <code>FileChooserActionType</code> 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.</p> + <p>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.</p> + <p>Nuestro primer ejemplo usará <code>FileChooserWidget</code> , que puede emplearse en el modo + Abrir y Salvar.</p> + <pre class="codebox">fileChooserWidgetNew :: FileChooserAction -> IO FileChooserWidget +</pre> + <p>Aquí usamos <code>FileChooserActionOpen</code>, y cuando el usuario elige definitivamente un fichero, ya sea + haciendo doble clic en él o pulsando la tecla Enter, la señal <code>onFileActived</code> se emite. Usamos:</p> [_$_] + <pre class="codebox">fileChooserGetFilename :: FileChooserClass self => self -> IO (Maybe FilePath) +</pre> + <p>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 <code>FileChooser</code> para formatos URI (Uniform Resource Identifier), + pero no las vamos a ver aquí.</p> + <p>Puedes permitir al usuario seleccionar múltiples ficheros con:</p> + <pre class="codebox">fileChooserSetselectMultiple :: FileChooserClass self => self -> Bool -> IO () +</pre> + <p>y, con el <code>FileChooserWidget</code> , 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:</p> + <pre class="codebox">fileChooserSetExtraWidget :: (FileChooserClass self, WidgetClass extraWidget) +=> self -> extraWidget -> IO () +</pre> + <p>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.</p> + <p>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.</p> + <pre class="codebox"> hsfilt <- fileFilterNew + fileFilterAddPattern hsfilt "*.hs" + fileFilterSetName hsfilt "Haskell Source" [_$_] + fileChooserAddFilter fch hsfilt +</pre> + <p>Puedes también añadir un widget "preview" (previsualización) con:</p> + <pre class="codebox">fileChooserSetPreviewWidget :: (FileChooserClass self, WidgetClass +previewWidget) => self -> previewWidget -> IO () +</pre> + <p>En el ejemplo se usa para previsualizar ficheros gráficos. El ejemplo usa un widget [_$_] + <code>Image</code> (documentado en Graphics.UI.Gtk.Display.Image) como los usados antes en el + Capítulo 4.1. + Allí usamos <code>imageNewFromFile</code> para añadir gráficos a un botón; aquí construímos + un widget <code>Image</code> vacío.</p> + <p>Para actualizarlo cuando haya cambios, tenemos una señal <code>onUpdatePreview</code>, + 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:</p> + <pre class="codebox"> onUpdatePreview fch $ [_$_] + do file <- fileChooserGetPreviewFilename fch + case file of + Nothing -> putStrLn "No File Selected" + Just fpath -> imageSetFromFile img fpath +</pre> + <p>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:</p> + <p> + <img src="Images/GtkChap5-2a.png" alt="File Selection examples" id="imgGtkChap5-2a" /> + </p> + <p>Fíjate en que el usuario también puede añadir y borrar bookmarks, y [_$_] + <code>FileChooser</code> tiene funciones para gestionar esto también. Sin + embargo, esta característica no se trata en el ejemplo <code>FileChooserWidget</code> , que + tiene el siguiente código fuente:</p> + <pre class="codebox">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 +</pre> + <p class="notebox">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.</p> + <p>El segundo modo de usar el interface <code>FileChooser</code> es a través de <code>FileChooserButton</code> .</p> + <pre class="codebox">fileChooserButtonNew :: String FileChooserAction -> String -> +IO FileChooserButton +</pre> + <p>El parámetro tipo <code>String</code> 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í.</p> + <p> + <img src="Images/GtkChap5-2b1.png" alt="File Selection examples" id="imgGtkChap5-2b1" /> + </p> + <p>Así es como se vería la ventana de diálogo:</p> + <p> + <img src="Images/GtkChap5-2b2.png" alt="File Selection examples" id="imgGtkChap5-2b2" /> + </p> + <p>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:</p> + <p> + <img src="Images/GtkChap5-2b3.png" alt="File Selection examples" id="imgGtkChap5-2b3" /> + </p> + <p>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 <code>onCurrentFolderChanged</code> 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 <code>onCurrentFolderChanged</code> también puede ser usada. Aquí está el código del ejemplo:</p> + <pre class="codebox">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 +</pre> + <p>La tercera manera de usar el interfaz <code>FileChooser</code> es a través de [_$_] + <code>FileChooserDialog</code> . Puede ser construido en modo abrir o salvar, y normalmente se aplica desde [_$_] + un menú o una barra de herramientas.</p> + <p> + <code>FileChooserDialog</code> implementa tanto [_$_] + <code>FileChooser</code> como [_$_] + <code>Dialog</code> . Recuerda del Capítulo 4.5 que un "diálogo" es un widget compuesto con botones, normalmente + implementados con <code>dialogRun</code>, que produce respuestas del tipo [_$_] + <code>ResponseId</code> . Un [_$_] + <code>FileChooserDialog</code> se construye con:</p> + <pre class="codebox">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 +</pre> + <p>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.</p> + <p>El ejemplo usa [_$_] + <code>FileChooserActionSave</code> y la ventana de diálogo tiene tres botones. Así es como queda:</p> + <p> + <img src="Images/GtkChap5-2c.png" alt="File Selection examples" id="imgGtkChap5-2c" /> + </p> + <p>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:</p> + <pre class="codebox">fileChooserSetDoOverwriteconfirmation :: FileChooserClass self +=> self -> Bool -> IO () +</pre> + <p>Como ya mencioné, no se realizan escrituras o sobrescrituras de ficheros con el + widget <code>FileChooserDialog</code>; El programa simplemente obtiene el path del fichero.</p> + <p>Este es el código del tercer ejemplo:</p> + <pre class="codebox">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 +</pre> + <p class="notebox">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 <code>onConfirmOverwrite</code> y [_$_] + su segundo argumento de tipo <code>IO FileChooserConfirmation.</code> No entiendo bien su uso, + y quizá el error provenga de mi código.</p> + <div id="footer"> + <span class="nav-previous"> + <a href="es-chap5-1.xhtml">Previo</a> + <br />Calendario</span> + <span class="nav-home"> + <a href="es-index.xhtml">Inicio</a> + </span> + <span class="nav-next"> + <a href="es-chap5-3.xhtml">Siguiente</a> + <br />Seleción de Fuente (tipo de letra)</span> + </div></body></html> addfile ./docs/tutorial/Tutorial_Port/es-chap5-3.xhtml hunk ./docs/tutorial/Tutorial_Port/es-chap5-3.xhtml 1 - +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="es"><head> + [_$_] + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <title>Tutorial de Gtk2Hs: Seleción de Fuente (tipo de letra)</title> + [_$_] + <link href="default.css" type="text/css" rel="stylesheet" /></head><body> + <div id="header"> + <h1>Tutorial de Gtk2Hs</h1> + <span class="nav-previous"> + <a href="es-chap5-2.xhtml">Previo</a> + </span> + <span class="nav-home"> + <a href="es-index.xhtml">Inicio</a> + </span> + <span class="nav-next"> + <a href="es-chap5-4.xhtml">Siguiente [_$_] + </a></span> + </div> + <h2>5.3 Seleción de Fuente (tipo de letra)</h2> + <p>La selección del color y del tipo de letra son muy parecidas + a la selección de ficheros. Hay tres maneras de implantarlas, como widgets, + como diálogos y como botones. Los valores seleccionados por el usuario se + obtienen a partir de atributos y funciones, como ya va resultando habitual. + Primero discutiremos la selección de tipo de letra (font). Puedes usar:</p> + <pre class="codebox">fontSelectionNew :: IO FontSelection +fontSelectionDialogNew :: String -> IO FontSelectionDialog +fontButtonNew :: IO FontButton +</pre> + <p>El parámetro [_$_] + <code>String</code> es el título de la ventana de diálogo. Hay un puñado de [_$_] + atributos y funciones para gestionar la presentación de estos widgets, todos bastante + sencillos. Con un diálogo debes usar los tipos <code>ResponseId</code> adecuados; con + el <code>FontButton</code> debes usar:</p> + <pre class="codebox">onFontSet:: FontButtonClass self => self -> IO () -> IO (ConnectId self) +</pre> + <p>Después puedes usar la siguiente función para conseguir el nombre de la fuente seleccionada [_$_] + por el usuario:</p> + <pre class="codebox">fontButtonGetFontName :: FontButtonClass self => self -> IO String +</pre> + <p>El nombre de la fuente debe ser algo así como "Courier Italic 10" o + "URW Gothic L Semi-Bold Oblique 16", dependiendo de lo que esté disponible en [_$_] + tu sistema. Como puedes ver en la imagen, el usuario puede seleccionar una familia, + un estilo y un tamaño.</p> + <p> + <img src="Images/GtkChap5-3a.png" alt="Font Select Window" id="imgGtkChap5-3a" /> + </p> + <p>La documentación sobre fonts está en Graphics.UI.Gtk.Pango.Font. Se soportan diversas [_$_] + características avanzadas, pero el usuario normal sólo necesita saber como conseguir una + <code>FontDescription</code> (descripción de fuente) a partir de un nombre de fuente.</p> + <pre class="codebox">fontDescriptionFromString :: String -> IO FontDescription +</pre> + <p>Una vez que tienes la <code>FontDescription</code> (descripción de una fuente), puedes [_$_] + usar:</p> + <pre class="codebox">widgetModifyFont:: WidgetClass self => self -> Maybe FontDescription -> IO () +</pre> + <p>La selección de color es parecida a la selección de fuentes. Tienes tres posibilidades: + </p> + <pre class="codebox">colorSelectionNew :: IO Color Selection +colorSelectionDialogNew: :: String -> IO ColorSelectionDialog +colorButtonNew :: IO Color Button +</pre> + <p>Con un ColorButton usa:</p> + <pre class="codebox">onColorSet :: ColorButtonClass self => self -> IO () -> IO (ConnectId self) +</pre> + <p>y después:</p> + <pre class="codebox">colorButtonGetColor :: ColorButtonClass self => self -> IO Color +</pre> + <p>También hay una función (y un atributo) para conseguir el valor Alpha (opacidad), + si esta característica ha sido activada.</p> + <p>La ventana de selección de color que aparece por defecto tiene esta forma:</p> + <p> + <img src="Images/GtkChap5-3b.png" alt="Color Selection Window" id="imgGtkChap5-3b" /> + </p> + <p>Color es un tipo de datos de tres <code>Ints</code> , en un rango de 0 A 65535, que + especifican los valores de los componentes rojo, verde y azul. Aquí hay funciones que [_$_] + permiten establecer los colores del foreground, background, texto y base de un widget, y + estas funciones usan un parámetro de tipo <code>StateType</code>. Estos son sus valores: [_$_] + <code>StateNormal, StateActive, StatePreLight, StateSelected + y StateInsensitive</code> y dependen de si el widget está activo, el puntero del ratón está + sobre un widget, se selecciona un widget y cosas así. Hay muchos parámetros que gobiernan la [_$_] + presentación de los widgets, por ejemplo, para cambiar el color de una etiqueta de texto + simplemente debes usar <code>StateNormal</code> y el <code>Color</code> que haya sido + seleccionado por el usuario.</p> + <pre class="codebox">widgetModifyFg :: WidgetClass self => self -> StateType -> Color -> IO () +</pre> + <p>Si tienes dudas sobre cual es el <code>StateType</code> que tiene el widget, puedes usar la siguiente [_$_] + función:</p> + <pre class="codebox">widgetGetState :: WidgetClass w => w -> IO StateType +</pre> + <p>Aquí hay un ejemplo de selección de fuente y color.</p> + <p> + <img src="Images/GtkChap5-3c.png" alt="FontButton and ColorButton Example" id="imgGtkChap5-3c" /> + </p> + <p>La ventana automáticamente cambia de tamaño para que quepa la fuente mayor.</p> + <p> + <img src="Images/GtkChap5-3d.png" alt="Window" id="imgGtkChap5-3d" /> + </p> + <pre class="codebox">import Graphics.UI.Gtk + +main :: IO () +main = do + initGUI + window <- windowNew + set window [windowTitle := "Font and Color Selection", +containerBorderWidth := 10 ] + vb <- vBoxNew False 0 + containerAdd window vb + + qtlab <- labelNew (Just "How poor are they that have not +patience!\nWhat wound did ever heal but by degrees?\nThou know'st +we work by wit, and not by witchcraft;\nAnd wit depends on dilatory +time.") + boxPackStart vb qtlab PackGrow 0 + + srclab <- labelNew (Just "From Othello (II, iii, 376-379)") + srcfont <- fontDescriptionFromString "Courier Italic 10" + widgetModifyFont srclab (Just srcfont) + miscSetAlignment srclab 1.0 0.5 + boxPackStart vb srclab PackNatural 10 + + sep <- hSeparatorNew + boxPackStart vb sep PackGrow 10 + [_$_] + fntb <- fontButtonNew + boxPackStart vb fntb PackGrow 0 + + colb <- colorButtonNew + boxPackStart vb colb PackGrow 0 + + onFontSet fntb $ do name <- fontButtonGetFontName fntb + fdesc <- fontDescriptionFromString name + widgetModifyFont qtlab (Just fdesc) + putStrLn name + + onColorSet colb $ do colour <- colorButtonGetColor colb + widgetModifyFg qtlab StateNormal colour + putStrLn (show colour) + + widgetShowAll window + onDestroy window mainQuit + mainGUI + +instance Show Color where + show (Color r g b) = "Red: " ++ (show r) ++ [_$_] + " Green: " ++ (show g) ++ [_$_] + " Blue: " ++ (show b) +</pre> + <div id="footer"> + <span class="nav-previous"> + <a href="es-chap5-2.xhtml">Previo</a> + <br />5.2 Selección de fichero</span> + <span class="nav-home"> + <a href="es-index.xhtml">Inicio</a> + <br /> </span> + <span class="nav-next"> + <a href="es-chap5-4.xhtml">Siguiente [_$_] + </a> + <br />5.4 Bloc de notas</span> + </div> + </body></html> addfile ./docs/tutorial/Tutorial_Port/es-chap5-4.xhtml hunk ./docs/tutorial/Tutorial_Port/es-chap5-4.xhtml 1 - +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="es"><head> + [_$_] + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <title>Tutorial de Gtk2Hs: Bloc de notas</title> + [_$_] + <link href="default.css" type="text/css" rel="stylesheet" /></head><body> + <div id="header"> + <h1>Tutorial de Gtk2Hs</h1> + <span class="nav-previous"> + <a href="es-chap5-3.html">Previo</a> + </span> + <span class="nav-home"> + <a href="es-index.html">Inicio</a> + </span> + <span class="nav-next"> + <a href="es-chap6-1.html">Siguiente</a> + </span> + </div> + <h2>5.4 Bloc de notas</h2> + <p>El widget [_$_] + <code>Notebook</code> (bloc de notas) es una colección de "páginas" que + se superponen. Cada página es diferente, y sólo una es visible en cada + momento. Las páginas contienen otros widgets que el programador suministra.</p> + <p>Para crear un nuevo widget Notebook:</p> + <pre class="codebox">NotebookNew :: IO Notebook +</pre> + <p>Una vez que el bloc de notas ha sido creado, dispones de funciones y atributos + para ajustarlo a tus necesidades o gustos. Los siguientes atributos determinan la [_$_] + posición de las pestañas (tabs), y si son visibles o no.</p> + <pre class="codebox">notebookTabPos :: NotebookClass self => Attr self PositionType +notebookShowTabs :: NotebookClass self => Attr self Bool +</pre> + <p>PositionType (tipo de posición) tiene los siguientes cosntructores : [_$_] + <code>PosLeft</code> , [_$_] + <code>PosRight</code> , [_$_] + <code>PosTop</code> (por defecto) y [_$_] + <code>PosBottom.</code> </p> + <p>A continuación echaremos un vistazo a la manera de añadir páginas + al notebook. Hay tres modos, append (añadir detrás), prepend (añadir delante) e insert (insertar).</p> + <pre class="codebox">noteBookAppendPage :: (NotebookClass self, WidgetClass child) +=> self +-> child -- El widget que tiene los contenidos de la página +-> String -- la etiqueta de texto +-> IO Int -- el índice (número de página) de la nueva página (empieza en 0) +</pre> + <p>La función [_$_] + <code>notebookPrependPage</code> tiene la misma signatura. + Y, por supuesto, devuelve 0 como valor del índice. La función [_$_] + <code>notebookInsertPage</code> toma el índice (lugar donde quieres insertar + la página) como un parámetro adicional. Se pueden eliminar páginas + con [_$_] + <code>notebookRemovePage.</code> </p> + <p>Un [_$_] + <code>Notebook</code> es un widget contenedor y puedes usar otros contenedores como + hijos, incluyendo cajas horizontales y verticales. Esto te permite crear páginas + bastante complejas, y establecer su distribución con las funciones de empaquetado + habituales.</p> + <p>Las funciones listadas para añadir, pre-añadir e insertar páginas, sólo sirven + con etiquetas de texto. Las tres tienen versiones que permiten que aparezca un menú + emergente (popup), y en los cuales puedes usar cualquier widget como etiqueta.</p> + <pre class="codebox">notebookAppendPageMenu :: +(NotebookClass self, WidgetClass child, WidgetClass tabLabel, WidgetClass menuLabel) +=> self +-> child -- el widget contenido en la página +-> tabLabel -- el widget para usar como etiqueta de la página +-> menuLabel -- el widget para usar como etiqueta del menú emergente +-> IO Int -- el índice (número de página) de la nueva página (empieza en 0) +</pre> + <p> + <code>notebookPrependPageMenu </code> y [_$_] + <code> notebookInsertPageMenu</code> colocarán la página en primer lugar o en + la posición indicada por el índice respectivamente.</p> + <p>Algunos atributos interesantes son: (consulta la docuemntación de la API (en inglés) [_$_] + para verlos todos):</p> + <pre class="codebox">notebookScrollable :: NotebookClass self => Attr self Bool +notebookCurrentPage :: NotebookClass self => Attr self Int +notebookEnablePopup :: NotebookClass self => Attr self Bool +</pre> + <p>Si hay muchas páginas puedes usar [_$_] + <code>notebookScrollable</code> . Usa [_$_] + <code>notebookCurrentPage</code> o la función [_$_] + <code>notebookSetCurrentPage</code> para abrir el notebook en una página diferente + que la primera (valor por defecto). El atributo [_$_] + <code>notebookEnablePopup</code> determina si la pulsación del botón derecho + del ratón en una pestaña mostrará un menú emergente de todas las páginas disponibles, [_$_] + siempre que las funciones de menú hayan sido definidas.</p> + <p>Un widget + <code>Notebook</code> tiene su propia función de manejo de la señal:</p> + <pre class="codebox">onSwitchPage :: NotebookClass nb => nb -> (Int -> IO ()) -> IO (ConnectId nb) +</pre> + <p>La función, que tú debes suministrar, emplea un índice de página devuelto por + <code>onSwitchPage</code> y debe realizar alguna salida.</p> + <p>Los ejemplos muestran un catálogo <code>StockItem</code> de conjuntos de iconos + de maneras diversas.</p> + <p> + <img src="Images/GtkChap5-4a.png" alt="Notebook Example 1" id="imgGtkChap5-4a" /> + </p> + <p>Vimos los Stock items en el capítulo 4.5. Recuerda + que un <code>StockItem</code> se conoce a partir de GTK+ (y Gtk2Hs). + La siguiente función produce una lista de todos los identificadores de Stock Items.</p> + <pre class="codebox">stockListIds :: IO [StockId] +</pre> + <p>Un [_$_] + <code>StockId</code> es una [_$_] + <code>String</code> y en Gtk2Hs tiene la forma: [_$_] + <code>stockCopy</code> , [_$_] + <code>stockDialogError</code> etc. En GTK+ la forma correspondiente + es: gtk-copy, gtk-dialog-error y así sucesivamente. El ejemplo + define una función tabName para convertir los identificadores GTK+ en + la lista de StockId a nombres para las solapas del notebook. La función + <code>myNewPage</code> usa [_$_] + <code>imageNewFromStock</code> para poner el icono en un widget [_$_] + <code>Image</code>, que será después añadido a la página. Devuelve + el índice de la página, pero no lo usa. Para conseguir una lista de todas las + páginas puedes usar <code>sequence</code> en vez de [_$_] + <code>sequence_</code> </p> + <p>Fíjate en que el tamaño del icono, en píxeles debe ser limitado. El + valor por defecto es 4, el valor usado aquí, 6, también está permitido + pero un tamaño de 8 produce un error de ejecución con GHCi.</p> + <pre class="codebox">import Graphics.UI.Gtk +import Data.Char (toUpper) + +main :: IO () +main= do + initGUI + window <- windowNew + set window [windowTitle := "Notebook Example 1", windowDefaultWidth := 300, + windowDefaultHeight := 200 ] + [_$_] + ntbk <- notebookNew + containerAdd window ntbk + set ntbk [notebookScrollable := True, notebookTabPos := PosBottom] + + stls <- stockListIds + sequence_ (map (myNewPage ntbk) stls) + + onSwitchPage ntbk (putStrLn . ((++)"Page: ") . show) + + widgetShowAll window + onDestroy window mainQuit + mainGUI + +tabName :: StockId -> String +tabName st = (drop 3) (conv st) where + conv (x:[]) = x:[] + conv (x:y:ys) | x == '-' = (toUpper y):(conv ys) + | otherwise = x: (conv (y:ys)) + +myNewPage :: Notebook -> StockId -> IO Int +myNewPage noteb stk = [_$_] + do img <- imageNewFromStock stk 6 + pagenum <- notebookAppendPage noteb img (tabName stk) + return pagenum [_$_] +</pre> + <p>Otra manera de mostrar el catálogo es poner los iconos en las solapas + del notebook.</p> + <p> + <img src="Images/GtkChap5-4b.png" alt="Notebook Example 2" id="imgGtkChap5-4a" /> + </p> + <p>Para hacer esto necesitamos el estilo de menú para añadir páginas, y también [_$_] + hemos definido un menú de solapas que consta de la primera letra de la cadena + nombre. El resultado es un menú emergente de 98 letras, con desplazamiento. [_$_] + Esto puede ser inhabilitado de un modo sencillo + a través del atributo <code>notebookEnablePopup</code>. El contenido de cada + página es el identificador de icono de Gtk2Hs (mira + Graphics.UI.Gtk.General.StockItems).</p> + <pre class="codebox">import Graphics.UI.Gtk +import Data.Char (toUpper) + +main :: IO () +main= do + initGUI + window <- windowNew + set window [windowTitle := "Notebook Example 2", windowDefaultWidth := 300, + windowDefaultHeight := 200 ] + [_$_] + ntbk <- notebookNew + containerAdd window ntbk + set ntbk [notebookScrollable := True, notebookEnablePopup := True, + notebookTabPos := PosRight ] + + stls <- stockListIds + sequence_ (map (myNewPage ntbk) stls) + + onSwitchPage ntbk (putStrLn . ((++)"Page: ") . show) + + widgetShowAll window + onDestroy window mainQuit + mainGUI + +tabName :: StockId -> String +tabName st = (drop 3) (conv st) where + conv (x:[]) = x:[] + conv (x:y:ys) | x == '-' = (toUpper y):(conv ys) + | otherwise = x: (conv (y:ys)) + +myNewPage :: Notebook -> StockId -> IO Int +myNewPage noteb stk = [_$_] + do img <- imageNewFromStock stk 4 + let nmstr = tabName stk + men <- labelNew (Just ((take 1) nmstr)) + cont <- labelNew (Just ("stock" ++ nmstr)) + pagenum <- notebookAppendPageMenu noteb cont img men + return pagenum [_$_] +</pre> + <div id="footer"> + <span class="nav-previous"> + <a href="es-chap5-3.html">Previo</a> + <br />5.3 Seleción de Fuente (tipo de letra)</span> + <span class="nav-home"> + <a href="es-index.html">Inicio</a> + </span> + <span class="nav-next"> + <a href="es-chap6-1.html">Siguiente</a> + <br />6.1 Ventanas con desplazamiento (scroll)</span> + </div> + [_$_] +</body></html> addfile ./docs/tutorial/Tutorial_Port/es-chap6-1.xhtml hunk ./docs/tutorial/Tutorial_Port/es-chap6-1.xhtml 1 - +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="es"><head> + [_$_] + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <title>Tutorial de Gtk2Hs: Ventanas con desplazamiento (scroll)</title> + [_$_] + <link href="default.css" type="text/css" rel="stylesheet" /></head><body> + <div id="header"> + <h1>Tutorial de Gtk2Hs</h1> + <span class="nav-previous"> + <a href="es-chap5-4.xhtml">Previo</a> + </span> + <span class="nav-home"> + <a href="es-index.xhtml">Inicio</a> + </span> + <span class="nav-next"> + <a href="es-chap6-2.xhtml">Siguiente</a> + </span> + </div> + <h2>6.1 Ventanas con desplazamiento (scroll)</h2> + <p>Las ventanas con desplazamiento se usan para crear un área desplazable con otro + widget dentro de él. Puedes insertar cualquier tipo de widget en una ventana desplazable, + y será accesible, sin tener en cuenta su tamaño, mediante el uso de las barras de desplazamiento [_$_] + (scroll bars)</p> + <p>La siguiente función se usa para crear una nueva ventana desplazable.</p> + <pre class="codebox">scrolledWindowNew :: Maybe Adjustment -> Maybe Adjustment -> IO ScrolledWindow +</pre> + <p>El primer argumento es el ajuste de la dirección horizontal, y el segundo el de la dirección vertical. + Casi siempre se establecen a + <code>Nothing</code> (nada) .</p> + <pre class="codebox">scrolledWindowSetPolicy :: ScrolledWindowClass self => +self -> PolicyType -> PolicyType -> IO () +</pre> + <p>Este establece la política a usar con respecto a las barras de desplazamiento verticales y [_$_] + horizontales. El constructor <code>PolicyAlways</code> muestra siempre la barra de desplazamiento, + <code>PolicyNever</code> no la muestra nunca y [_$_] + <code>PolicyAutomatic</code> la muestra sólo si el tamaño de la página es mayor que la ventana. + Por defecto usa [_$_] + <code>PolicyAlways</code>.</p> + <p>A continuación puedes colocar tu objeto en la ventana con desplazamiento usando + <code>containerAdd</code> si el objeto tiene una ventana asociada con él. Si no lo tiene, + necesitarás un <code>Viewport</code> , aunque puedes añadir una automáticamente con:</p> + <pre class="codebox">scrolledWindowAddWithViewport :: (ScrolledWindowClass self, WidgetClass child) +=> self -> child -> IO () +</pre> + <p>Si te olvidas del <code>viewport</code>, GHCi produce un mensaje de [_$_] + error si usas <code>containerAdd</code> y no has debido hacerlo.</p> + <p>En el ejemplo empaquetamos una tabla con 100 botones toggle en + una ventana con desplazamiento. Implementa un programa de [_$_] + 'Yet Another Haskell Tutorial' (y otro tutorial de Haskell) de Hal Daumé III. [_$_] + Este tutorial está disponible en forma gratuita en el web de Haskell. [_$_] + En la página 43 hay un programita que permite adivinar un numero entre 1 y 100, seleccionado + al azar por la máquina, indicando si nuestra selección es menor, mayor o igual. + El número se genera con la función <code>randomRIO</code> del módulo System.Random.</p> + <p>Nuestro ejemplo lo implementa con un interfaz gráfico.</p> + <p><img src="Images/GtkChap6-1.png" alt="Scrolled Window" id="imgGtkChap6-1" /></p> [_$_] + [_$_] + <p>En la ventana principal usamos una caja vertical para empaquetar + una etiqueta (para información del usuario), un separador horizontal, una + ventana con desplazamiento, un separador horizontal y una caja + horizontal que contiene dos botones de stock. + La ventana con desplazamiento se empaqueta con <code>PackGrow</code>, [_$_] + lo que le permite adaptarse a los cambios de tamaño de la ventana principal. + Los botones nuevo (new) y salir (quit) se empaquetan en los extremos + opuestos de la caja horizontal.</p> + <p>Los 100 botones se crean con:</p> + <pre class="codebox"> buttonlist <- sequence (map numButton [1..100]) +</pre> + <p>donde la función [_$_] + <code>numButton</code> se define como:</p> + <pre class="codebox">numButton :: Int -> IO Button +numButton n = do + button <- buttonNewWithLabel (show n) + return button +</pre> + <p>Así, cada botón automáticamente obtiene el número apropiado + como etiqueta.</p> + <p>Dentro de la ventana con desplazamiento creamos una tabla + de 10 por 10 para los 100 botones. Para posicionar los botones + usamos la función <code>cross</code> , que se basa en <code>List monad</code>. [_$_] + Esta función, un modo sencillo de obtener un producto cartesiano + de dos o más listas, también está explicado en el tutorial ya citado.</p> + <pre class="codebox">cross :: [Int] -> [Int] -> [(Int,Int)] +cross row col = do [_$_] + x <- row + y <- col + return (x,y) +</pre> + <p>La función [_$_] + <code>attachButton</code> parte de una tabla, un botón y una + tupla de coordenadas para colocar un botón en la tabla. (Repasa el + capítulo 3.3 para más información sobre empaquetado de tablas.)</p> + <pre class="codebox">attachButton :: Table -> Button -> (Int,Int) -> IO () [_$_] +attachButton ta bu (x,y) = tableAttachDefaults ta bu y (y+1) x (x+1) +</pre> + <p>Ahora, el siguiente segmento de código empaqueta todos los botones en [_$_] + la tabla con <code>buttonlist</code> según se ha descrito.</p> + <pre class="codebox"> let places = cross [0..9] [0..9] + sequence_ (zipWith (attachButton table) buttonlist places) +</pre> + <p>Cada vez que el usuario pulsa el botón play se genera un + número aleatorio, que habrá que ir comparando con la elección [_$_] + del usuario. Pero el manejador de señales de Gtk2Hs <code>onClicked</code> [_$_] + emplea un botón y una función sin parámetros y tiene un valor de tipo + <code>IO ()</code> . Necesitamos algo así como una variable global, + y esta es aportada por el módulo Data.IORef. Ahora podemos usar los siguientes [_$_] + snippets, en diferentes funciones, para inicializar, escribir y leer el + número aleatorio.</p> + <pre class="codebox">snippet 1 -- randstore <- newIORef 50 [_$_] +snippet 2 -- writeIORef rst rand [_$_] +snippet 3 -- rand <- readIORef rst +</pre> + <p>El primero obtiene una variable de tipo <code>IORef Int</code> y la + inicializa al valor 50. La segunda se implementa con la función aleatoria [_$_] + <code>randomButton</code> :</p> + <pre class="codebox">randomButton :: ButtonClass b => Label -> IORef Int -> b -> IO (ConnectId b) +randomButton inf rst b = [_$_] + onClicked b $ do rand <- randomRIO (1::Int, 100) + writeIORef rst rand [_$_] + set inf [labelLabel := "Ready"] + widgetModifyFg inf StateNormal (Color 0 0 65535) +</pre> + <p>y después usa el siguiente snippet, donde <code>info</code> [_$_] + es la etiqueta que permite acceder a la información de usuario. + (Mira el capítulo 5.3 para los colores y como cambiarlos.)</p> + <p>De un modo parecido a la escritura del número aleatorio, la + función <code>actionButton</code> implementa la lectura de [_$_] + <code>randstore.</code> Entonces compara el número obtenido [_$_] + de la etiqueta del botón que ha sido pulsado, y muestra la + información en la etiqueta <code>info</code>. </p> + <p>Finalmente debemos monitorizar los 100 botones para saber cual ha + sido pulsado, si ha habido alguno.</p> + <pre class="codebox"> sequence_ (map (actionButton info randstore) buttonlist) +</pre> + <p>Lo anterior es análogo a las combinaciones de <code>sequence</code> _ y [_$_] + <code>map</code> que hemos usado, pero en este caso exactamente + uno de los 100 manejadores de señal será activado, en el momento en + que el usuario pulse un botón concreto.</p> + <p>A continuación tienes el código completo del ejemplo.</p> + <pre class="codebox">import Graphics.UI.Gtk +import Data.IORef [_$_] +import System.Random (randomRIO) + +main:: IO () +main= do + initGUI + window <- windowNew + set window [ windowTitle := "Guess a Number", [_$_] + windowDefaultWidth := 300, windowDefaultHeight := 250] + mb <- vBoxNew False 0 + containerAdd window mb + + info <- labelNew (Just "Press \"New\" for a random number") + boxPackStart mb info PackNatural 7 + sep1 <- hSeparatorNew + boxPackStart mb sep1 PackNatural 7 + [_$_] + scrwin <- scrolledWindowNew Nothing Nothing + boxPackStart mb scrwin PackGrow 0 + + table <- tableNew 10 10 True + scrolledWindowAddWithViewport scrwin table + + buttonlist <- sequence (map numButton [1..100]) + let places = cross [0..9] [0..9] + sequence_ (zipWith (attachButton table) buttonlist places) + + sep2 <- hSeparatorNew + boxPackStart mb sep2 PackNatural 7 + hb <- hBoxNew False 0 + boxPackStart mb hb PackNatural 0 + play <- buttonNewFromStock stockNew + quit <- buttonNewFromStock stockQuit + boxPackStart hb play PackNatural 0 + boxPackEnd hb quit PackNatural 0 + [_$_] + randstore <- newIORef 50 + randomButton info randstore play + + sequence_ (map (actionButton info randstore) buttonlist) [_$_] + + widgetShowAll window + onClicked quit (widgetDestroy window) + onDestroy window mainQuit + mainGUI + +numButton :: Int -> IO Button +numButton n = do + button <- buttonNewWithLabel (show n) + return button + +cross :: [Int] -> [Int] -> [(Int,Int)] +cross row col = do [_$_] + x <- row + y <- col + return (x,y) + +attachButton :: Table -> Button -> (Int,Int) -> IO () +attachButton ta bu (x... [truncated message content] |