packages feed

AutoForms-0.4.0: src/Graphics/UI/AF/WxForm/GUI.hs

-- |The GUI type and helper functions
module Graphics.UI.AF.WxForm.GUI
    ( -- *GUI type
      GUI (Single, SelfContained, Buttons)
    -- ** GUI construting function
    , containerGUI, singleGui
      
    -- *Layout
    , LayoutManager
    , layoutGuiAs, dualColumn, singleColumn, singleRow, reverseGuis
    , rootGui
    
    -- * WxHaskell's Layout type
    -- 
    -- |Note that WxHaskell's Layout type is different from this
    --  modules Layout type.
    , boxedLayout, unboxedLayout, labeledLayout, nonEditableLayout

    -- * Misc
    , module Graphics.UI.AF.General.PriLabel, Labeled(..), LabelGetter(..)
    )

where


import qualified Graphics.UI.WX as WX
import Graphics.UI.AF.General.PriLabel

import List (partition)
import Maybe

-- |Contains a GUI-component. Used by 'EC'.
data GUI = Single WX.Layout PriLabel                         -- ^A GUI with a single GUI-component. 
         | SelfContained (String -> IO WX.Layout) PriLabel   -- ^Here the GUI handles the header itself.
         | Many LayoutManager [GUI] PriLabel                 -- ^A GUI with sub-GUI-components.
         | Buttons WX.Layout                                 -- ^A list of buttons.

-- SelfContained needs the IO, as WxEnumeration needs it. We need to
-- wait with setting the label, until after creating the GUI data, but
-- enumeration needs to do `set enum [ text := ... ]`. We need to wait
-- with setting the label, as it may be changed by functions like
-- `humanizeLabel`.
--
-- For the other constructors it is not a problem, as the GUI itself
-- do not set the label. The label is set by other means by other
-- functions. E.g. see `labeledLayout`.

class Labeled a where
    updateLabel :: (PriLabel -> PriLabel) -> a -> a
    setLabel    :: PriLabel -> a -> a
    setLabel lbl = updateLabel (const lbl)

class LabelGetter a where
    -- |Returns the label of a 'GUI'
    guiLabel :: a -> PriLabel

instance LabelGetter GUI where
    guiLabel (Single _ lbl)        = lbl
    guiLabel (SelfContained _ lbl) = lbl
    guiLabel (Many _ _ lbl)        = lbl
    guiLabel _                     = defaultLabel ""

instance Labeled GUI where
    updateLabel f (Single lay lbl)        = (Single lay $ f lbl)
    updateLabel f (SelfContained lay lbl) = (SelfContained lay $ f lbl)
    updateLabel f (Many manager guis lbl) = (Many manager guis $ f lbl)
    updateLabel _ gui                     = gui

instance Labeled PriLabel where
    updateLabel f lbl = f lbl

buttonsLabel :: PriLabel
buttonsLabel = defaultLabel "Press buttons"

-- |Constructs a GUI using Single-constructor.
singleGui :: (WX.Widget w) => PriLabel -> w -> (WX.Layout -> WX.Layout) -> GUI
singleGui label guiElement toWidget
    = Single (toWidget $ WX.widget guiElement) label

-- |Constructs a GUI using Many-constructor.
containerGUI :: [GUI] -> PriLabel -> GUI
containerGUI guis label = Many singleColumn guis label


--------------------------------------------------------------------------------

-- |Used to layout GUIs. If a GUI contains multiple components, the
-- layout manager arranges these components. Also see 'singleRow' or
-- 'singleColumn'.
type LayoutManager = [(WX.Layout, Maybe String)] -> WX.Layout

{-
defaultLayout :: LayoutManager
defaultLayout [] = error "GUI.defaultLayout: Must have at least one member of the list."
defaultLayout layoutsAndLabels =
    singleColumn $ mergeButtons layoutsAndLabels where
        mergeButtons
-}

-- FIXME: Consider using caption in stead of WX.label below:
-- Puts one or more Layouts in a column, without any heading (label).
singleColumn :: LayoutManager
singleColumn [] = error "GUI.singleColumn: Must have at least one member of the list."
singleColumn layoutsAndLabels =
    let (withLabels, withoutLabels) = partitionWidgets layoutsAndLabels
        isEmpty xs = (length xs) == 0
    in WX.column 10 $
       (if isEmpty withLabels
           then []
           else [WX.grid 20 10 withLabels]
       )
       ++ withoutLabels

-- |Arranges all gui components (widgets) in a row.
singleRow :: LayoutManager
singleRow [] = error "GUI.singleRow: Must have at least one member of the list."
singleRow layoutsAndLabels =
{-
    let (withLabels, withoutLabels) = partitionWidgets layoutsAndLabels
    in WX.row 10 $
       (concat withLabels)
       ++ withoutLabels
-}
    let labelWidgets (lay, Just name) = [WX.label name, lay]
        labelWidgets (lay, _)         = [lay]
    in WX.row 10 $ concatMap labelWidgets layoutsAndLabels

reverseGuis :: LayoutManager -> LayoutManager
reverseGuis manager layoutsAndLabels =
    manager $ reverse layoutsAndLabels


partitionWidgets :: [(WX.Layout, Maybe String)] -> ([[WX.Layout]], [WX.Layout])
partitionWidgets layoutsAndLabels =
    let sortedWidgets = partition (isJust . snd) layoutsAndLabels
        withLabels = map (\(lay, name) -> (lay, fromJust name)) (fst sortedWidgets)
        withoutLabels = map fst (snd sortedWidgets)
    in (map (\(lay,name) -> [WX.label name, lay]) withLabels, withoutLabels)

-- |Arranges all gui components (widgets) in a column.
dualColumn :: LayoutManager
dualColumn []  = error "AutoForms.GUI.dualColumn: Must have at least one member of the list."
dualColumn [x] = singleColumn [x]
dualColumn xs =
    let halfLength = length xs `div` 2
    in WX.row 35 (map singleColumn [take halfLength xs, drop halfLength xs])

-- |Attach a new layout manager to a 'GUI'. Layout manager such as
--  'dualColumn' or 'singleRow' can be used.
layoutGuiAs :: LayoutManager -> GUI -> GUI
layoutGuiAs newManager (Many _ guis label) = Many newManager guis label
layoutGuiAs _ gui = gui

-- |Special handling of a GUI if it is the top-most GUI.
rootGui :: GUI -> GUI
rootGui gui@(Many manager guis label) =
    case last guis of
      Buttons buttons -> let newButtons = Buttons (WX.column 10 [ WX.hfill $ WX.hrule 1
                                                                , buttons
                                                                ])
                         in Many manager (init guis ++ [newButtons]) label
      _ -> gui
rootGui gui = gui

-----------------------------------------------------------------------------

-- |Return a WxHaskell Layout with no editable values. E.g. just a label.
nonEditableLayout :: IO (WX.Layout, String)
nonEditableLayout = return (WX.label "No editable values!", "No editable values!")

-- |Turns a GUI into a WxHaskell Layout and a string (the label). The
--  layout has no box around it.
unboxedLayout :: GUI -> IO (WX.Layout, String)
unboxedLayout (Single lay label) = return (lay, labelString label)
unboxedLayout (SelfContained lay label)
    = do lay' <- lay $ labelString label
         return (lay', labelString label)
unboxedLayout (Many layoutManager [Many _ guis _] label)
    = unboxedLayout $ Many layoutManager guis label -- FIXME: should we push manager and label downwards?
unboxedLayout (Many _ [gui] _)
    = unboxedLayout gui
unboxedLayout (Many layoutManager guis label)
    = do layoutsAndLabels <- mapM boxedLayout guis
         return (layoutManager layoutsAndLabels, labelString label)
unboxedLayout (Buttons lay) = return (lay, labelString buttonsLabel)

-- |Returns a WxHaskell Layout. If possible the WxHaskell Layout will
-- be construced with a WxHaskell label to the left of the 'GUI'-s
-- Layout.
labeledLayout :: GUI -> IO WX.Layout
labeledLayout (Single lay label) = return $ WX.row 5 [ WX.label (labelString label), lay ]
labeledLayout (SelfContained lay label)
    = do lay' <- lay $ labelString label
         return lay'
labeledLayout (Many layoutManager [Many _ guis _] label)
    = labeledLayout $ Many layoutManager guis label -- FIXME: should we push manager and label downwards?
                                                    -- or maybe just use the best one
labeledLayout (Many _ [gui] _)
    = labeledLayout gui
labeledLayout (Many layoutManager guis _)
    = do layoutsAndLabels <- mapM boxedLayout guis
         return (layoutManager layoutsAndLabels)
labeledLayout (Buttons lay) = return lay

-- |Turns a GUI into a, possibly boxed, Layout.
--  A label will only be returned if GUI is of type Single.
--  If GUI's not of type Single, the Layout will contain its own label.
boxedLayout :: GUI -> IO (WX.Layout, Maybe String)
boxedLayout (Single lay label) = return (lay, Just $ labelString label)
boxedLayout (SelfContained lay label)
    = do lay' <- lay $ labelString label
         return (lay', Nothing)
boxedLayout (Many layoutManager [Many _ guis _] label)
    = boxedLayout $ Many layoutManager guis label -- FIXME: should we push manager and label downwards?
boxedLayout (Many _ [gui] _)
    = boxedLayout gui
boxedLayout (Many layoutManager guis label)
    = do guis' <- mapM boxedLayout guis
         return (WX.boxed (labelString label) (layoutManager guis'), Nothing)
boxedLayout (Buttons lay) = return (lay, Nothing)