{-# LANGUAGE Rank2Types #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE OverlappingInstances #-} -- | -- Description: Graphical Form Input -- -- This module defines 'SimpleForm's, a simple interface -- to filling in forms using HTk. (Indeed, it is simple enough that it might -- be ported to some other GUI sometime.) module HTk.Toolkit.SimpleForm( Form, -- This represents a series of input fields. -- A (Form x) represents a form yielding a value of type x -- Form is an instance of functor, so fmap works for it. -- But mapForm is more general. newFormEntry, -- :: (FormLabel label,FormValue value) -- => label -> value -> Form value -- This creates a new form with a single labelled entry. -- The FormValue class includes text fields and radio buttons. emptyForm, -- :: Form () -- The empty form (rather boring). nullForm, -- :: FormLabel label => label -> Form () -- also pretty boring; just displays the label but doesn't provide -- any interaction. newFormMenu, -- :: (FormLabel label) => label -> HTkMenu value -- -> Form (Maybe value) -- This creates a new form with a single labelled entry, selected -- by a menu. A value of Nothing indicates that the user did not -- click this menu. -- The String is used to label the menu button containing the menu. newFormOptionMenu, -- :: (GUIValue a) => [a] -> Form a -- This creates an "option menu" button. The -- advantage this has over a normal menu is that the value is shown. -- The first value in the list functions as a default value. newFormOptionMenu2, -- :: (GUIValue a) => [(a,b)] -> Form b -- Like newFormOptionMenu2 but returns the corresponding b value. (//), -- :: Form value1 -> Form value2 -> Form (value1,value2) -- This combines two forms. They will be displayed with one on top of -- the other. (\\), -- :: Form value1 -> Form value2 -> Form (value1,value2) -- Like //, but combines two forms side-by-side. column, -- :: [Form value] -> Form [value] row, -- :: [Form value] -> Form [value] -- Two other combinators obtained by iterating (//) and (\\) doForm, -- :: String -> Form x -> IO (Maybe x) -- This displays a form. The first string is the title; -- the second the form. As well as the entries in the form, -- "OK" and "Cancel" buttons are displayed. doFormMust, -- :: String -> Form value -> IO value -- Like doForm, but the user is not provided with a cancel button. doFormList, -- :: String -> [(Form x,String)] -> IO (Event (WithError x),IO ()) -- Display a sequence of forms, horizontally, one after another. -- To the right of each form is a button, with text given by the -- accompanying String. -- Clicking this button causes an event to be generated, carrying -- the accompanying form's value, or if invalid the error -- message. -- The first argument is the title of the window. The -- IO () action returned closes the window. mapForm, -- :: (x -> WithError y) -> Form x -> Form y -- mapForm changes the type of a form. When we press OK with doForm, -- the supplied function is called. If it returns a y, we return y -- and close the window; if it returns an error message, -- the error message is -- displayed, and we continue. mapFormIO, -- :: (x -> IO (WithError y)) -> Form x -> Form y -- IO'based version of mapForm. guardForm, -- :: (x -> Bool) -> String -> Form x -> Form x -- guardForm uses mapForm to check the value of x with the supplied -- error message. guardFormIO, -- :: (x -> IO Bool) -> String -> Form x -> Form x -- IO'based version of guardForm. guardNothing, -- :: String -> Form (Maybe x) -> Form x -- Checks that Nothing is not returned, with the attached error -- message. FormValue(..), -- This is a class of values which can be read in from a -- simple form. Instances include Int, String and Bool and (). -- (() just does nothing and is useful if you want a label without -- anything on it.) -- A user friendly way of constructing new instances is to instance -- one of the following two classes. mapMakeFormEntry, -- :: FormValue value2 -- => (value1 -> value2) -> (value2 -> value1) -- -> (Frame -> value1 -> IO (EnteredForm value1)) -- Function for creating one instance of FormValue from another. -- FormRadioButton(..), -- This class is used for types which are suitable -- for being read with radio buttons, for example a small enumeration. FormTextField(..), -- This class is used for types which can be -- read in using a text field. FormTextFieldIO(..), -- Slightly more general version allowing IO actions. Password(..), -- newtype alias which specifies that the given FormTextField(IO) -- instance should not be displayed on the screen, but replaced by -- '.' characters. FormLabel(..), -- This class represents things which can be used for -- labels in the form. Instances include String and Image. EmptyLabel(EmptyLabel), -- Another instance of FormLabel, which we use if we don't want a label. WrappedFormLabel(..), -- this is an existentially wrapped type around -- values of type FormLabel. Radio(..), -- type for wrapping round something to use radio buttons. HasConfigRadioButton(..), -- for setting fancy configurations for -- radio buttons. editableTextForm, -- :: [Config Editor] -> Form String -- A form for typing (possibly several lines of) editable text. editableTextForm0, -- :: [Config Editor] -> Form String -- Like 'editableTextForm' but no scrollbars are displayed. ) where import Data.Char import Data.IORef import Data.Typeable import Util.ExtendedPrelude import Util.BinaryAll(HasBinary(..),mapWrite,mapRead) import Util.Messages import Util.Computation import Events.Events import Events.Channels import HTk.Toplevel.HTk import HTk.Toolkit.HTkMenu -- ------------------------------------------------------------------------- -- The EnteredForm type -- ------------------------------------------------------------------------- -- | EnteredForm represents a form entry constructed in a given widget -- The actions should be performed in the following sequence: -- packAction -- 0 or more uses of getFormValue -- destroyAction data EnteredForm value = EnteredForm { packAction :: IO (), -- packs the form entry into the widget. getFormValue :: IO (WithError value), -- extracts value or produces an error message destroyAction :: IO () -- does any necessary clean-up. } mapEnteredForm :: (a -> b) -> EnteredForm a -> EnteredForm b mapEnteredForm f (EnteredForm{packAction = packAction,getFormValue = getFormValue, destroyAction = destroyAction}) = EnteredForm {packAction = packAction,destroyAction = destroyAction, getFormValue = do we1 <- getFormValue return (mapWithError f we1) } mapEnteredForm' :: (a -> WithError b) -> EnteredForm a -> EnteredForm b mapEnteredForm' f (EnteredForm{packAction = packAction,getFormValue = getFormValue, destroyAction = destroyAction}) = EnteredForm {packAction = packAction,destroyAction = destroyAction, getFormValue = do we1 <- getFormValue return (mapWithError' f we1) } mapEnteredFormIO' :: (a -> IO (WithError b)) -> EnteredForm a -> EnteredForm b mapEnteredFormIO' f (EnteredForm{packAction = packAction,getFormValue = getFormValue, destroyAction = destroyAction}) = EnteredForm {packAction = packAction,destroyAction = destroyAction, getFormValue = do we1 <- getFormValue mapWithErrorIO' f we1 } -- ------------------------------------------------------------------------- -- The Form type and (//) -- ------------------------------------------------------------------------- newtype Form value = Form (forall container . Container container => container -> IO (EnteredForm value)) instance Functor Form where fmap f (Form getEnteredForm0) = let getEnteredForm1 container = do enteredForm1 <- getEnteredForm0 container return (mapEnteredForm f enteredForm1) in Form getEnteredForm1 mapForm :: (x -> WithError y) -> Form x -> Form y mapForm f (Form getEnteredForm0) = let getEnteredForm1 container = do enteredForm1 <- getEnteredForm0 container return (mapEnteredForm' f enteredForm1) in Form getEnteredForm1 mapFormIO :: (x -> IO (WithError y)) -> Form x -> Form y mapFormIO f (Form getEnteredForm0) = let getEnteredForm1 container = do enteredForm1 <- getEnteredForm0 container return (mapEnteredFormIO' f enteredForm1) in Form getEnteredForm1 infixr 8 // -- so it binds less tightly than \\ (//) :: Form value1 -> Form value2 -> Form (value1,value2) (//) (Form enterForm1) (Form enterForm2) = let enterForm container = do enteredForm1 <- enterForm1 container enteredForm2 <- enterForm2 container let enteredForm = EnteredForm { packAction = ( do packAction enteredForm1 packAction enteredForm2 ), getFormValue = ( do valueError1 <- getFormValue enteredForm1 valueError2 <- getFormValue enteredForm2 return (pairWithError valueError1 valueError2) ), destroyAction = ( do destroyAction enteredForm1 destroyAction enteredForm2 ) } return enteredForm in Form enterForm guardForm :: (x -> Bool) -> String -> Form x -> Form x guardForm test mess = mapForm (\x -> if test x then hasValue x else hasError mess) guardFormIO :: (x -> IO Bool) -> String -> Form x -> Form x guardFormIO test mess = mapFormIO (\ x -> do res <- test x return (if res then hasValue x else hasError mess) ) guardNothing :: String -> Form (Maybe x) -> Form x guardNothing mess = mapForm (\ xOpt -> case xOpt of Nothing -> hasError mess Just x -> hasValue x ) -- ------------------------------------------------------------------------- -- The \\ function -- ------------------------------------------------------------------------- (\\) :: Form x -> Form y -> Form (x,y) (\\) (Form enterForm1) (Form enterForm2) = let enterForm container = -- This is somewhat clumsy as we can't specify the pack action -- of the internal forms, so have to wrap them in two further forms. do frame <- newFrame container [] frame1 <- newFrame frame [] enteredForm1 <- enterForm1 frame1 frame2 <- newFrame frame [] enteredForm2 <- enterForm2 frame2 let enteredForm = EnteredForm { packAction = ( do packAction enteredForm1 pack frame1 [Side AtLeft] packAction enteredForm2 pack frame2 [Side AtLeft] pack frame [] ), getFormValue = ( do valueError1 <- getFormValue enteredForm1 valueError2 <- getFormValue enteredForm2 return (pairWithError valueError1 valueError2) ), destroyAction = ( do destroyAction enteredForm1 destroyAction enteredForm2 ) } return enteredForm in Form enterForm infixr 9 \\ -- so it binds more tightly than // -- ------------------------------------------------------------------------- -- emptyForm, nullForm, column and row -- ------------------------------------------------------------------------- emptyForm :: Form () emptyForm = Form (\ container -> return (EnteredForm { packAction = done, getFormValue = return (hasValue ()), destroyAction = done }) ) nullForm :: FormLabel label => label -> Form () nullForm label = newFormEntry label () emptyFormList :: Form [a] emptyFormList = fmap (const []) emptyForm column :: [Form value] -> Form [value] column forms = foldr (\ form listForm -> fmap (uncurry (:)) (form // listForm)) emptyFormList forms row :: [Form value] -> Form [value] row forms = foldr (\ form listForm -> fmap (uncurry (:)) (form // listForm)) emptyFormList forms -- ------------------------------------------------------------------------- -- The doForm action -- ------------------------------------------------------------------------- doFormMust :: String -> Form value -> IO value doFormMust title form = do (Just value) <- doForm1 False title form return value doForm :: String -> Form value -> IO (Maybe value) doForm = doForm1 True doForm1 :: Bool -> String -> Form value -> IO (Maybe value) doForm1 canCancel title (Form enterForm) = do (toplevel,enteredForm,okEvent,cancelEvent) <- delayWish ( do toplevel <- createToplevel [text title] enteredForm0 <- enterForm toplevel -- create frame for "OK" and "Cancel" buttons. frame <- newFrame toplevel [] okButton <- newButton frame [text "OK"] okEvent <- clicked okButton packAction enteredForm0 pack okButton [Side AtLeft] cancelEvent <- if canCancel then do cancelButton <- newButton frame [text "Cancel"] pack cancelButton [Side AtRight] clicked cancelButton else return never (destroyEvent,cancelBind) <- bindSimple toplevel Destroy let enteredForm = enteredForm0 { destroyAction = do destroyAction enteredForm0 cancelBind } pack frame [Side AtTop] return (toplevel,enteredForm,okEvent,cancelEvent +> destroyEvent) ) let handler = (do okEvent always ( do valueError <- getFormValue enteredForm case fromWithError valueError of Right value -> return (Just value) Left err -> do errorMess err sync handler ) ) +> (do cancelEvent return Nothing ) valueOpt <- sync handler -- finish off destroyAction enteredForm destroy toplevel return valueOpt doFormList :: String -> [(Form x,String)] -> IO (Event (WithError x),IO ()) doFormList title (formList :: [(Form x,String)]) = do let doOneForm :: Toplevel -> (Form x,String) -> IO (Event (WithError x),IO ()) doOneForm toplevel (Form enterForm,buttonName) = do frame <- newFrame toplevel [] leftFrame <- newFrame frame [] enteredForm <- enterForm leftFrame button <- newButton frame [text buttonName,anchor East] clickEvent <- clicked button packAction enteredForm pack leftFrame [Side AtLeft,Anchor West] pack button [Side AtRight,Anchor East] pack frame [Side AtTop,Fill X] let handler = clickEvent >>> getFormValue enteredForm return (handler,done) (toplevel,enterResults) <- delayWish ( do toplevel <- createToplevel [text title] enterResults <- mapM (doOneForm toplevel) formList return (toplevel,enterResults) ) (destroyEvent,unbind) <- bindSimple toplevel Destroy let event0 = choose (map fst enterResults) event1 = event0 +> (do destroyEvent return (fail "Window destroyed") ) destroyWindow :: IO () destroyWindow = do mapM_ snd enterResults unbind destroy toplevel return (event1,destroyWindow) -- ------------------------------------------------------------------------- -- newFormEntry -- ------------------------------------------------------------------------- newFormEntry :: (FormLabel label,FormValue value) => label -> value -> Form value newFormEntry label value = let enterForm container = do frame <- newFrame container [] packLabel <- formLabel frame label enteredForm1 <- makeFormEntry frame value let enteredForm = EnteredForm { packAction = ( do packLabel packAction enteredForm1 pack frame [Side AtTop,Fill X] ), getFormValue = getFormValue enteredForm1, destroyAction = destroyAction enteredForm1 } return enteredForm in Form enterForm -- ------------------------------------------------------------------------- -- newFormMenu -- ------------------------------------------------------------------------- newFormMenu :: FormLabel label => label -> HTkMenu value -> Form (Maybe value) newFormMenu label htkMenu = let enterForm container = do frame <- newFrame container [] packLabel <- formLabel frame label enteredForm1 <- makeFormMenuEntry frame htkMenu let enteredForm = EnteredForm { packAction = ( do packLabel packAction enteredForm1 pack frame [Side AtTop,Fill X] ), getFormValue = getFormValue enteredForm1, destroyAction = destroyAction enteredForm1 } return enteredForm in Form enterForm makeFormMenuEntry :: Frame -> HTkMenu value -> IO (EnteredForm (Maybe (value))) makeFormMenuEntry frame htkMenu = do (menuButton,menuEvent) <- compileHTkMenu frame htkMenu -- Set up things for the thread which watches for menu events so that -- it picks up the last one. resultRef <- newIORef Nothing -- put the result here! killChannel <- newChannel -- terminate watcher thread here! let menuEventThread = (do menuClick <- menuEvent always (writeIORef resultRef (Just menuClick)) menuEventThread ) +> receive killChannel _ <- spawnEvent menuEventThread return (EnteredForm{ packAction = pack menuButton [], getFormValue = ( do valueOpt <- readIORef resultRef return (hasValue valueOpt) ), destroyAction = sync (send killChannel ()) }) -- ------------------------------------------------------------------------- -- newFormOptionMenu -- ------------------------------------------------------------------------- newFormOptionMenu :: (GUIValue a) => [a] -> Form a newFormOptionMenu options = let enterForm container = do optionMenu <- newOptionMenu container options [] return (EnteredForm { packAction = pack optionMenu [], getFormValue = ( do val <- getValue optionMenu return (hasValue val) ), destroyAction = done }) in Form enterForm newFormOptionMenu2 :: (Eq a,GUIValue a) => [(a,b)] -> Form b newFormOptionMenu2 options = let form1 = newFormOptionMenu (map fst options) in fmap (\ a0 -> case findJust (\ (a1,b1) -> if a1 == a0 then Just b1 else Nothing) options of Nothing -> error ( "SimpleForm.newFormOptionMenu2: HTk returned strange value") Just b -> b ) form1 -- ------------------------------------------------------------------------- -- The FormLabel class -- This is used for labels of fields in the form, and also for labels -- of radio buttons. -- ------------------------------------------------------------------------- class FormLabel label where formLabel :: Frame -> label -> IO (IO ()) -- formLabel frame label creates a new label -- (normally at the left of) the frame "frame" with detail label. The action -- returned is the packing action. instance FormLabel String where formLabel frame str = do label <- newLabel frame [text str,anchor West] return (pack label [Side AtLeft,Fill X]) instance FormLabel Image where formLabel frame image = do label <- newLabel frame [photo image] return (pack label [Side AtLeft]) -- We provide a heterogenous version of this too. data WrappedFormLabel = forall label . FormLabel label => WrappedFormLabel label instance FormLabel WrappedFormLabel where formLabel frame (WrappedFormLabel label) = formLabel frame label -- Finally, a label which actually does nothing at all. data EmptyLabel = EmptyLabel instance FormLabel EmptyLabel where formLabel _ _ = return done -- ------------------------------------------------------------------------- -- The FormValue class -- ------------------------------------------------------------------------- class FormValue value where makeFormEntry :: Frame -> value -> IO (EnteredForm value) -- Create a new form entry, given a default value. mapMakeFormEntry :: FormValue value2 => (value1 -> value2) -> (value2 -> value1) -> (Frame -> value1 -> IO (EnteredForm value1)) mapMakeFormEntry toValue2 fromValue2 frame value1 = do enteredForm <- makeFormEntry frame (toValue2 value1) return (mapEnteredForm fromValue2 enteredForm) -- ------------------------------------------------------------------------- -- Instance #1 - FormTextField's, corresponding to a single line of text. -- ------------------------------------------------------------------------- class FormTextField value where makeFormString :: value -> String -- used for computing the initial string from the given default value readFormString :: String -> WithError value -- readFormString computes the value, or an error message. -- Two examples -- strings instance FormTextField String where makeFormString str = str readFormString str = hasValue str allSpaces :: String -> Bool allSpaces = all isSpace -- numbers instance (Num a,Show a,Read a) => FormTextField a where makeFormString value = show value readFormString str = case reads str of [(value,rest)] | allSpaces rest -> hasValue value _ -> hasError (show str ++ " is not a number") instance FormTextField value => FormTextFieldIO value where makeFormStringIO value = return (makeFormString value) readFormStringIO value = return (readFormString value) -- ------------------------------------------------------------------------- -- Instance #1A - FormTextFieldIO's, where IO actions are allowed -- ------------------------------------------------------------------------- class FormTextFieldIO value where makeFormStringIO :: value -> IO String readFormStringIO :: String -> IO (WithError value) instance FormTextFieldIO value => FormValue value where makeFormEntry frame defaultVal = do defaultString <- makeFormStringIO defaultVal contentsVariable <- createTkVariable defaultString (entry :: Entry String) <- newEntry frame [variable contentsVariable] let getFormValue = do (contents :: String) <- readTkVariable contentsVariable readFormStringIO contents let enteredForm = EnteredForm { packAction = pack entry [Side AtRight,Fill X], getFormValue = getFormValue, destroyAction = done } return enteredForm -- ------------------------------------------------------------------------- -- Instance #1B - A variation of the former, for a text field where the -- characters are not displayed as typed in, but replaced by '.' -- ------------------------------------------------------------------------- newtype Password value = Password value instance FormTextFieldIO value => FormValue (Password value) where makeFormEntry frame (Password defaultVal) = do defaultString <- makeFormStringIO defaultVal contentsVariable <- createTkVariable defaultString (entry :: Entry String) <- newEntry frame [showText '.',variable contentsVariable] let getFormValue = do (contents :: String) <- readTkVariable contentsVariable valueWE <- readFormStringIO contents return (mapWithError Password valueWE) let enteredForm = EnteredForm { packAction = pack entry [Side AtRight,Fill X], getFormValue = getFormValue, destroyAction = done } return enteredForm -- ------------------------------------------------------------------------- -- Instance #2B. Maybe something that's an instance of FormTextFieldIO -- so corresponding to Maybe String or Maybe Number. -- It is possible to nest FormTextFieldIO's Maybe(Maybe . . .) but this is -- not recommended. -- When reading a null string, this will be parsed as a value rather than -- Nothing if possible; this happens for example with String. -- ------------------------------------------------------------------------- instance FormTextFieldIO value => FormTextFieldIO (Maybe value) where makeFormStringIO Nothing = return "" makeFormStringIO (Just value) = makeFormStringIO value readFormStringIO "" = do null <- readFormStringIO "" return (case fromWithError null of Left _ -> hasValue Nothing Right x -> hasValue (Just x) ) readFormStringIO str = do xWE <- readFormStringIO str return (mapWithError Just xWE) -- ------------------------------------------------------------------------- -- Instance #2C - Radio Buttons -- If "x" is an instance of "Show", "Bounded" and "Enum", "Radio x" will be an -- instance of FormValue, and will display the buttons in order. -- But if you don't like this define your own instances of Show or, -- for pictures, HasConfigRadioButton. -- -- Radio Int is _not_ recommended. -- ------------------------------------------------------------------------- data Radio x = Radio x | NoRadio deriving (Typeable) -- The NoRadio indicates that no radio button is selected. class HasConfigRadioButton value where configRadioButton :: value -> Config (RadioButton Int) -- instance Show value => HasConfigRadioButton value where -- configRadioButton value = text (show value) instance (HasConfigRadioButton value,Bounded value,Enum value) => FormValue (Radio value) where makeFormEntry frame rvalue = do let minB :: value minB = minBound maxB :: value maxB = maxBound minBoundInt :: Int minBoundInt = fromEnum minB maxBoundInt :: Int maxBoundInt = fromEnum maxB fromRValue :: Radio value -> Int fromRValue NoRadio = -1 fromRValue (Radio x) = fromEnum x - minBoundInt toRValue :: Int -> Radio value toRValue (-1) = NoRadio toRValue i = if i>= 0 && i<= maxBoundInt - minBoundInt then Radio (toEnum (i+minBoundInt)) else error ("SimpleForm.toRValue - radio button with odd number:"++ show i) radioVar <- createTkVariable (fromRValue rvalue) -- Add the radio buttons and get their packing actions. packActions <- mapM (\ val -> do radioButton <- newRadioButton frame [ configRadioButton val, variable radioVar, value (fromRValue (Radio val)) ] return (pack radioButton [Side AtLeft]) ) [minB .. maxB] let enteredForm = EnteredForm { packAction = sequence_ packActions, getFormValue = do valInt <- readTkVariable radioVar return (hasValue (toRValue valInt)), destroyAction = done } return enteredForm -- We need elsewhere in the workbench a Binary instance for Radio instance (Monad m,HasBinary x m) => HasBinary (Radio x) m where writeBin = mapWrite (\ radio -> case radio of Radio x -> Just x NoRadio -> Nothing ) readBin = mapRead (\ xOpt -> case xOpt of Just x -> Radio x Nothing -> NoRadio ) -- ------------------------------------------------------------------------- -- Instance #3 - Check buttons a.k.a. Bools. -- ------------------------------------------------------------------------- instance FormValue Bool where makeFormEntry frame b = do boolVar <- createTkVariable b checkButton <- newCheckButton frame [variable boolVar] let enteredForm = EnteredForm { packAction = pack checkButton [Side AtLeft], getFormValue = ( do bool <- readTkVariable boolVar return (hasValue bool) ), destroyAction = done } return enteredForm -- ------------------------------------------------------------------------- -- () -- ------------------------------------------------------------------------- instance FormValue () where makeFormEntry frame () = return ( EnteredForm { packAction = done, getFormValue = return (hasValue ()), destroyAction = done } ) -- ------------------------------------------------------------------------- -- An editable text window as a form entry. -- ------------------------------------------------------------------------- -- | An editable text window as a form entry -- Useful config options: -- (value String) to set initial contents -- (height i), (width i) to set the height and width in characters. -- (background s) to set the background colour to s. editableTextForm :: [Config Editor] -> Form String editableTextForm configs = Form (\ container -> do editorFrame <- newFrame container [] editor <- newEditor editorFrame (configs ++ [wrap NoWrap]) scrollBar1 <- newScrollBar editorFrame [orient Vertical] scrollBar2 <- newScrollBar container [orient Horizontal] editor # scrollbar Vertical scrollBar1 editor # scrollbar Horizontal scrollBar2 return (EnteredForm { packAction = (do pack editor [Side AtRight] pack scrollBar1 [Side AtRight,Fill Y,Expand On] pack editorFrame [] pack scrollBar2 [Side AtTop,Fill X,Expand On] ), getFormValue = ( do value <- getValue editor return (hasValue value) ), destroyAction = done }) ) -- | Like 'editableTextForm' but no scrollbars are displayed. editableTextForm0 :: [Config Editor] -> Form String editableTextForm0 configs = Form (\ container -> do editorFrame <- newFrame container [] editor <- newEditor editorFrame (configs ++ [wrap NoWrap]) return (EnteredForm { packAction = (do pack editor [Side AtRight] pack editorFrame [] ), getFormValue = ( do value <- getValue editor return (hasValue value) ), destroyAction = done }) )