packages feed

lhae-0.0.3: src/View/Dialog/Complex.hs

module View.Dialog.Complex (Layout (..),Widget (..),Modifier (..)
                           ,showDialog,showSimpleDialog
                           ,okLabel,okButton,cancelButton)
    where

import Control.Applicative ((<$>))
import Control.Monad (forM,forM_,foldM,when)
import Data.Char (isDigit)
import Graphics.UI.WX (Prop ((:=)),on)
import qualified Graphics.UI.WX as WX
import qualified Graphics.UI.WXCore as WXC
import View (View,frame)
import Util (justWhen,maybeRead)
import Util.Color (Color,fromWXColor,toWXColor)
import I18n (__)

data Widget a b = 
    ScrolledWindow (WX.Dialog () -> IO (WX.ScrolledWindow ()))
  | TextEntry (a -> String) (a -> String -> a)
  | NumberEntry (a -> Double) (a -> Double -> a)
  | Button String b
  | DefaultButton String b
  | TerminationButton String
  | ColorButton (a -> Color) (a -> Color -> a)
  | SingleListBox [String] (a -> Maybe Int) (a -> Maybe Int -> a)
  | MultiListBox [String] (a -> [Int]) (a -> [Int] -> a)
  | Spinner Int Int (a -> Int) (a -> Int -> a)
  | Choice [String] (a -> Int) (a -> Int -> a)
  | CheckBox String (a -> Bool) (a -> Bool -> a)
  | RadioBox String [String] (a -> Int) (a -> Int -> a)

data Modifier = HFill | Center | Margin | Boxed String | MinSize (Int,Int)

data WXWidget a b = 
    WXScrolledWindow (WX.ScrolledWindow ())
  | WXTextEntry (WX.TextCtrl ()) (a -> String -> a)
  | WXNumberEntry (WX.TextCtrl ()) (a -> Double -> a)
  | WXButton (WX.Button ()) b
  | WXTerminationButton (WX.Button ())
  | WXColorButton (WX.Button ()) (a -> Color -> a)
  | WXSingleListBox (WX.SingleListBox ()) (a -> Maybe Int -> a)
  | WXMultiListBox (WX.MultiListBox ()) (a -> [Int] -> a)
  | WXSpinCtrl (WX.SpinCtrl ()) (a -> Int -> a)
  | WXChoice (WX.Choice ()) (a -> Int -> a)
  | WXCheckBox (WX.CheckBox ()) (a -> Bool -> a)
  | WXRadioBox (WX.RadioBox ()) (a -> Int -> a)

instance WX.Widget (WXWidget a b) where
    widget (WXScrolledWindow ctrl) = WX.widget ctrl
    widget (WXTextEntry ctrl _) = WX.widget ctrl
    widget (WXNumberEntry ctrl _) = WX.widget ctrl
    widget (WXButton ctrl _) = WX.widget ctrl
    widget (WXTerminationButton ctrl) = WX.widget ctrl
    widget (WXColorButton ctrl _) = WX.widget ctrl
    widget (WXSingleListBox ctrl _) = WX.widget ctrl
    widget (WXMultiListBox ctrl _) = WX.widget ctrl
    widget (WXSpinCtrl ctrl _) = WX.widget ctrl
    widget (WXChoice ctrl _) = WX.widget ctrl
    widget (WXCheckBox ctrl _) = WX.widget ctrl
    widget (WXRadioBox ctrl _) = WX.widget ctrl

data Layout a b = Row [Layout a b]
                | Column [Layout a b]
                | Grid [[Layout a b]]
                | Label String
                | Modifier Modifier (Layout a b)
                | Modifiers [Modifier] (Layout a b)
                | Widget (Widget a b)
                | FocusOn (Widget a b)
                | WXWidget (WXWidget a b)

showDialog :: String -> Layout a b -> a -> View -> IO (Maybe (a,b))
showDialog caption layout defaultRecord view = do
  dialog <- WX.dialog (frame view) [WX.text := caption]
  layout' <- buildWXWidgets dialog layout defaultRecord

  WX.set dialog [WX.layout := buildWXLayout layout']

  WX.showModal dialog 
    (\endWith -> setButtonActions endWith defaultRecord layout')

showSimpleDialog :: String -> Layout a () -> a -> View -> IO (Maybe a)
showSimpleDialog caption layout defaultRecord view = 
    fmap fst <$> showDialog caption layout defaultRecord view

buildWXWidgets :: WX.Dialog () -> Layout a b -> a -> IO (Layout a b)
buildWXWidgets dialog layout record =
    let recBuild layout' = buildWXWidgets dialog layout' record
    in
      case layout of
        Row a -> Row <$> forM a recBuild
        Column a -> Column <$> forM a recBuild
        Grid a -> Grid <$> mapM (mapM recBuild) a
        Label a -> return $ Label a
        Modifier a b -> Modifier a <$> recBuild b
        Modifiers a b -> Modifiers a <$> recBuild b
        FocusOn a -> do (WXWidget w) <- recBuild $ Widget a
                        case w of  WXTextEntry c _ -> WX.focusOn c
                                   WXNumberEntry c _ -> WX.focusOn c
                                   WXButton c _ -> WX.focusOn c
                        return $ WXWidget w
        Widget (ScrolledWindow makeWindow) -> 
            (WXWidget . WXScrolledWindow) <$> makeWindow dialog
        Widget (TextEntry get set) ->
            do ctrl <- WX.textEntry dialog [WX.text := get record]
               return $ WXWidget $ WXTextEntry ctrl set
        Widget (NumberEntry get set) ->
            do ctrl <- WX.textEntry dialog [WX.text := show $ get record]
               WX.set ctrl [on WX.anyKey := numberEntryKeyHandler ctrl]
               return $ WXWidget $ WXNumberEntry ctrl set
        Widget (Button text action) -> 
            do ctrl <- WX.button dialog [WX.text := text]
               return $ WXWidget $ WXButton ctrl action
        Widget (DefaultButton text action) -> 
            do ctrl <- WX.button dialog [WX.text := text]
               WXC.buttonSetDefault ctrl
               return $ WXWidget $ WXButton ctrl action
        Widget (TerminationButton text) -> 
            do ctrl <- WX.button dialog [WX.text := text]
               return $ WXWidget $ WXTerminationButton ctrl
        Widget (ColorButton get set) -> 
            do ctrl <- WX.button dialog [ WX.bgcolor := toWXColor $ get record]               
               WX.set ctrl [on WX.command := do
                 color <- WX.get ctrl WX.bgcolor >>= WX.colorDialog dialog
                 justWhen color $ \c -> WX.set ctrl [WX.bgcolor := c]]
               return $ WXWidget $ WXColorButton ctrl set
        Widget (SingleListBox items get set) ->
            do ctrl <- WX.singleListBox dialog [WX.items := items]
               justWhen (get record) $ \selection -> 
                   WX.set ctrl [WX.selection := selection]
               return $ WXWidget $ WXSingleListBox ctrl set
        Widget (MultiListBox items get set) ->
            do ctrl <- WX.multiListBox dialog [WX.items := items]
               when (not $ null $ get record) $
                    WX.set ctrl [WX.selections := get record]
               return $ WXWidget $ WXMultiListBox ctrl set
        Widget (Spinner min max get set) ->
            do ctrl <- WX.spinCtrl dialog min max [WX.selection := get record]
               return $ WXWidget $ WXSpinCtrl ctrl set
        Widget (Choice items get set) ->
            do ctrl <- WX.choice dialog [ WX.items := items
                                        , WX.selection := get record]
               return $ WXWidget $ WXChoice ctrl set
        Widget (CheckBox text get set) ->
            do ctrl <- WX.checkBox dialog [ WX.text := text
                                          , WX.checked := get record]
               return $ WXWidget $ WXCheckBox ctrl set
        Widget (RadioBox text items get set) ->
            do ctrl <- WX.radioBox dialog WX.Vertical items
                       [WX.text := text, WX.selection := get record]
               return $ WXWidget $ WXRadioBox ctrl set

buildWXLayout :: Layout a b -> WX.Layout
buildWXLayout layout = 
    case layout of
      Row a -> WX.row 5 $ map buildWXLayout a
      Column a -> WX.column 5 $ map buildWXLayout a
      Grid rows -> WX.grid 5 5 $ map (map buildWXLayout) rows
      Label a -> WX.label a
      Modifier HFill a -> WX.hfill $ buildWXLayout a
      Modifier Center a -> WX.centre $ buildWXLayout a
      Modifier Margin a -> WX.margin 10 $ buildWXLayout a
      Modifier (Boxed text) a -> WX.boxed text $ buildWXLayout a
      Modifier (MinSize (x,y)) a -> WX.minsize (WXC.sz x y) $ buildWXLayout a
      Modifiers xs a -> buildWXLayout $ foldr Modifier a xs
      WXWidget a -> WX.widget a

setButtonActions :: (Maybe (a,b) -> IO ()) -> a -> Layout a b -> IO ()
setButtonActions endWith record completeLayout = 
    let recSet layout =
            case layout of
              Row a -> forM_ a recSet
              Column a -> forM_ a recSet
              Grid rows -> mapM_ (mapM_ recSet) rows
              Label _ -> return ()
              Modifier _ a -> recSet a
              Modifiers _ a -> recSet a
              WXWidget (WXTerminationButton button) ->
                  WX.set button [on WX.command := endWith Nothing]
              WXWidget (WXButton button action) ->
                  WX.set button [on WX.command := 
                    do result <- buildRecord record completeLayout
                       endWith $ Just (result,action)]
              WXWidget _ -> return ()      
    in
      recSet completeLayout

buildRecord :: a -> Layout a b -> IO a
buildRecord record layout = 
    case layout of
      Row a -> foldM buildRecord record a
      Column a -> foldM buildRecord record a
      Grid rows -> foldM (foldM buildRecord) record rows
      Label _ -> return record
      Modifier _ a -> buildRecord record a
      Modifiers _ a -> buildRecord record a
      WXWidget (WXScrolledWindow _) -> return record
      WXWidget (WXTextEntry ctrl set) -> 
          set record <$> WX.get ctrl WX.text
      WXWidget (WXNumberEntry ctrl set) -> do
          num <- maybeRead <$> WX.get ctrl WX.text
          case num of Just n -> return $ set record n
                      Nothing -> return record
      WXWidget (WXButton _ _) -> return record
      WXWidget (WXTerminationButton _) -> return record
      WXWidget (WXColorButton ctrl set) -> 
          (set record . fromWXColor) <$> WX.get ctrl WX.bgcolor
      WXWidget (WXSingleListBox ctrl set) -> 
          set record <$> getMaybeSelection ctrl
      WXWidget (WXMultiListBox ctrl set) -> 
          set record <$> WX.get ctrl WX.selections
      WXWidget (WXSpinCtrl ctrl set) -> set record <$> WX.get ctrl WX.selection
      WXWidget (WXChoice ctrl set) -> set record <$> WX.get ctrl WX.selection
      WXWidget (WXCheckBox ctrl set) -> set record <$> WX.get ctrl WX.checked
      WXWidget (WXRadioBox ctrl set) -> set record <$> WX.get ctrl WX.selection

    where
      getMaybeSelection ctrl = do
        selection <- WX.get ctrl WX.selection
        return $ if selection == -1 then Nothing else Just selection

numberEntryKeyHandler :: (WX.TextCtrl ()) -> WX.Key -> IO ()
numberEntryKeyHandler ctrl key = do
  text <- WX.get ctrl WX.text
  let lastCharIsDigit = (not $ null text) && 
                        (isDigit $ head $ reverse text)
  case key of
    WX.KeyChar '0' -> WX.propagateEvent
    WX.KeyChar '1' -> WX.propagateEvent
    WX.KeyChar '2' -> WX.propagateEvent
    WX.KeyChar '3' -> WX.propagateEvent
    WX.KeyChar '4' -> WX.propagateEvent
    WX.KeyChar '5' -> WX.propagateEvent
    WX.KeyChar '6' -> WX.propagateEvent
    WX.KeyChar '7' -> WX.propagateEvent
    WX.KeyChar '8' -> WX.propagateEvent
    WX.KeyChar '9' -> WX.propagateEvent
    WX.KeyChar '.' -> when (not (elem '.' text) && lastCharIsDigit) 
                        WX.propagateEvent
    WX.KeyChar '-' -> when (null text) WX.propagateEvent
    WX.KeyChar _ -> return ()
    WX.KeySpace -> return ()
    _ -> WX.propagateEvent

okLabel :: String
okLabel = __ "&Ok"

okButton :: b -> Layout a b
okButton = Widget . DefaultButton okLabel

cancelButton :: Layout a b
cancelButton = Widget $ TerminationButton (__ "&Cancel")