packages feed

Blobs-0.1: src/XTC.hs

{-# OPTIONS -fglasgow-exts  #-}
{-
  | Module      :  XTC
    Maintainer  :  martijn@cs.uu.nl
    
    eXtended & Typed Controls for wxHaskell
    
    
    TODO: - how to handle duplicates (up to presentation) in item lists
          - check (!!) error that occured in Dazzle
          - implement tSelecting and other events
          - Check: instance selection etc. <Control> () or <Control> a
               - Maybe it should be () to prevent subclassing (which may cause a problem
                 with the client data field
          - Items w String??
          - value of selection when nothing selected? add Maybe?
          - WxObject vs Object?
-}

module XTC ( Labeled( toLabel ),
           , TValued( tValue ),
           , TItems( tItems ),
           , TSelection( tSelection ),
           , TSelections( tSelections ),
           , RadioView, mkRadioView, mkRadioViewEx
           , ListView, mkListView, mkListViewEx
           , MultiListView, mkMultiListView, mkMultiListViewEx
           , ChoiceView, mkChoiceView, mkChoiceViewEx
           , ComboView, mkComboView, mkComboViewEx
           , ValueEntry, mkValueEntry, mkValueEntryEx
           , change -- TODO wx should take care of this
--           , ObservableVar, mkObservableVar  -- temporarily disabled due to name clash
           , xtc -- for testing, exported to avoid a warning in Dazzle
           ) where

import Graphics.UI.WX hiding (window, label)
import qualified Graphics.UI.WX
import Graphics.UI.WXCore hiding (label, Event)
import List
import Maybe

class Labeled x where
  toLabel :: x -> String

instance Labeled String where
  toLabel str = str

class Selection w => TSelection x w | w -> x where
  tSelection :: Attr w x

class Selections w => TSelections x w | w -> x where
  tSelections :: Attr w [x]

class Items w String => TItems x w | w -> x where
  tItems :: Attr w [x]


-- RadioView

data CRadioView x b

type RadioView x b = RadioBox (CRadioView x b)

-- TODO: instance of tItems?
instance Labeled x => TSelection x (RadioView x ()) where
  tSelection
    = newAttr "tSelection" viewGetTSelection viewSetTSelection

mkRadioView :: Labeled x => Window a -> Orientation -> [x] -> [Prop (RadioView x ())] -> IO (RadioView x ())
mkRadioView window orientation viewItems props = 
  mkRadioViewEx window toLabel orientation viewItems props

mkRadioViewEx :: Window a -> (x -> String) -> Orientation -> [x] -> [Prop (RadioView x ())] -> IO (RadioView x ())
mkRadioViewEx window present orientation viewItems props = 
 do { model <- varCreate viewItems 
    ; radioView <- fmap objectCast $ radioBox window orientation (map present viewItems) []
    ; objectSetClientData radioView (return ()) (model, present)
    ; set radioView props
    ; return radioView
    } -- cannot use mkViewEx because items must be set at creation (items is not writeable)

-- ListView

data CListView a b

type ListView a b = SingleListBox (CListView a b)

instance TSelection x (ListView x ()) where
  tSelection = newAttr "tSelection" viewGetTSelection viewSetTSelection

instance TItems x (ListView x ()) where
  tItems = newAttr "tItems" viewGetTItems viewSetTItems

mkListView :: Labeled x => Window a -> [Prop (ListView x ())] -> IO (ListView x ())
mkListView window props = mkListViewEx window toLabel props
  
mkListViewEx :: Window a -> (x -> String) -> [Prop (ListView x ())] -> IO (ListView x ())
mkListViewEx window present props = mkViewEx singleListBox window present props


-- MultiListView

data CMultiListView a b

type MultiListView a b = MultiListBox (CMultiListView a b)

instance Labeled x => TSelections x (MultiListView x ()) where
  tSelections = newAttr "tSelections" multiListViewGetTSelections multiListViewSetTSelections

instance Labeled x => TItems x (MultiListView x ()) where
  tItems = newAttr "tItems" viewGetTItems viewSetTItems

mkMultiListView :: Labeled x => Window a -> [Prop (MultiListView x ())] -> IO (MultiListView x ())
mkMultiListView window props = mkMultiListViewEx window toLabel props

mkMultiListViewEx :: Window a -> (x -> String) -> [Prop (MultiListView x ())] -> IO (MultiListView x ())
mkMultiListViewEx window present props = mkViewEx multiListBox window present props

multiListViewSetTSelections :: MultiListView x () -> [x] -> IO ()
multiListViewSetTSelections (multiListView :: MultiListView x ()) selectionItems =
 do { Just ((model, present) :: (Var [x], x -> String)) <-
        unsafeObjectGetClientData multiListView
    ; viewItems <- get model value
    ; let labels = map present selectionItems
    ; let indices = catMaybes [ findIndex (\it -> present it == label) viewItems
                              | label <- labels ]
    ; set multiListView [ selections := indices ]
    }

multiListViewGetTSelections :: MultiListView x () -> IO [x]
multiListViewGetTSelections multiListView =
 do { Just ((model, _) :: (Var [x], x -> String)) <-
        unsafeObjectGetClientData multiListView
    ; selectedIndices <- get multiListView selections
    ; viewItems <- get model value
    ; return (map (safeIndex "XTC.multiListViewGetTSelections" viewItems)
                    selectedIndices)
    }


-- ChoiceView

data CChoiceView a b

type ChoiceView a b = Choice (CChoiceView a b)

instance Selecting (ChoiceView x ()) where
  select = newEvent "select" choiceGetOnCommand choiceOnCommand
-- Necessary because wxHaskell declares "instance Selecting (Choice ())" instead of
-- "Selecting (Choice a)". TODO: let/make Daan fix this

instance Selection (ChoiceView x ()) where
  selection = newAttr "selection" choiceGetSelection choiceSetSelection
-- Necessary because wxHaskell declares "instance Selection (Choice ())" instead of
-- "Selection (Choice a)".

instance TSelection x (ChoiceView x ()) where
  tSelection = newAttr "tSelection" viewGetTSelection viewSetTSelection

instance TItems x (ChoiceView x ()) where
  tItems = newAttr "tItems" viewGetTItems viewSetTItems

mkChoiceView :: Labeled x => Window a -> [Prop (ChoiceView x ())] -> IO (ChoiceView x ())
mkChoiceView window (props :: [Prop (ChoiceView x ())]) =
  mkViewEx choice window (toLabel :: x -> String) props

mkChoiceViewEx :: Window a -> (x -> String) -> Style -> [Prop (ChoiceView x ())] -> IO (ChoiceView x ())
mkChoiceViewEx window present stl props =
  mkViewEx (\win -> choiceEx win stl) window present props


-- ComboView

data CComboView a b

type ComboView a b = ComboBox (CComboView a b)


instance TSelection x (ComboView x ()) where
  tSelection = newAttr "tSelection" viewGetTSelection viewSetTSelection

instance TItems x (ComboView x ()) where
  tItems = newAttr "tItems" viewGetTItems viewSetTItems

mkComboView :: Labeled x => Window a -> [Prop (ComboView x ())] -> IO (ComboView x ())
mkComboView window (props :: [Prop (ComboView x ())]) =
  mkViewEx comboBox window (toLabel :: x -> String) props

mkComboViewEx :: Window a -> (x -> String) -> Style -> [Prop (ComboView x ())] -> IO (ComboView x ())
mkComboViewEx window present stl props = 
  mkViewEx (\win -> comboBoxEx win stl) window present props



-- generic mk function that puts a model and a present function in the client data
mkViewEx :: (parent -> [p] -> IO (Object a)) -> parent -> (x -> String) -> [Prop (WxObject b)] ->
            IO (WxObject b)
mkViewEx mkView window present props =
 do { model <- varCreate []
    ; view <- fmap objectCast $ mkView window []
    ; objectSetClientData view (return ()) (model, present)
    ; set view props
    ; return view
    }

-- generic set/getTSelection for RadioView, ListView, and ChoiceView

viewGetTSelection :: TSelection x (WxObject a) => WxObject a -> IO x
viewGetTSelection view =
 do { Just ((model, _) :: (Var [x], x -> String)) <-
        unsafeObjectGetClientData view
    ; selectedIndex <- get view selection
    ; viewItems <- get model value
    ; return (safeIndex "XTC.viewGetTSelection" viewItems selectedIndex)
    }

-- if non unique, set to first viewItem with same label
-- selection is set to 0 if object is not found, maybe -1 is better?
viewSetTSelection :: TSelection x (WxObject a) => WxObject a -> x -> IO ()
viewSetTSelection view selectionItem =
 do { Just ((model, present) :: (Var [x], x -> String)) <-
        unsafeObjectGetClientData view
    ; viewItems <- get model value
    ; let label = present selectionItem
    ; let index = findLabelIndex present label viewItems
    ; set view [ selection := index ]
    }
 where findLabelIndex :: (x -> String) -> String -> [x] -> Int
       findLabelIndex present label theItems =
         case findIndex (\it -> present it == label) theItems of
           Just ix -> ix
           Nothing -> 0

viewGetTItems :: TItems x (WxObject a) => WxObject a -> IO [x]
viewGetTItems view =
 do { Just ((model, _) :: (Var [x], x -> String)) <-
        unsafeObjectGetClientData view
    ; viewItems <- get model value
    ; return viewItems
    }

viewSetTItems :: TItems x (WxObject a) => WxObject a -> [x] -> IO ()
viewSetTItems view viewItems =
 do { Just ((model, present) :: (Var [x], x -> String)) <-
        unsafeObjectGetClientData view
    ; set model [ value := viewItems ]
    ; set view [ items := map present viewItems ]
    }






-- ValueEntry

class Parseable x where
  parse :: String -> Maybe x

instance Parseable String where
  parse = Just

{- When a type is instance of Read, a simple Parseable instance can be declared with readParse
   e.g. for Int:  instance Parseable Int where parse = readParse

TODO: can we make this some kind of default?
-}
readParse :: Read x => String -> Maybe x 
readParse str = case reads str of
                  [(x, "")] -> Just x
                  _         -> Nothing

class TValued  x w | w -> x where
  tValue :: Attr w (Maybe x)

data CValueEntry x b

type ValueEntry x b = TextCtrl (CValueEntry x b)

instance TValued x (ValueEntry x ()) where
  tValue
    = newAttr "tValue" valueEntryGetTValue valueEntrySetTValue

mkValueEntry :: (Show x, Read x) => Window b -> [ Prop (ValueEntry x ()) ] -> IO (ValueEntry x ())
mkValueEntry window props = mkValueEntryEx window show readParse props
                  
mkValueEntryEx :: Window b -> (x -> String) -> (String -> Maybe x) -> [ Prop (ValueEntry x ()) ] -> IO (ValueEntry x ())
mkValueEntryEx window present parse props =
 do { valueEntry <- fmap objectCast $ textEntry window []
    ; objectSetClientData valueEntry (return ()) (present, parse) 
    ; set valueEntry $ props ++ [ on change := validate valueEntry ]
                                          
    ; return valueEntry
    }
 where validate :: ValueEntry x () -> IO ()
       validate valueEntry =
        do { mVal <- get valueEntry tValue
           ; set valueEntry [ bgcolor := case mVal of -- TODO: add property for error color?
                                           Nothing -> lightgrey
                                           _       -> white
                            ]
           ; repaint valueEntry
           } -- drawing a squiggly doesn't work because font metrics are not available

valueEntryGetTValue :: ValueEntry x () -> IO (Maybe x)
valueEntryGetTValue valueEntry =
 do { Just ((_, parse) :: (x -> String, String -> Maybe x)) <- unsafeObjectGetClientData valueEntry
    ; valueStr <- get valueEntry text
    ; return $ parse valueStr
    }

valueEntrySetTValue :: ValueEntry x () -> Maybe x -> IO ()
valueEntrySetTValue valueEntry mValue =
 do { Just ((present, _) :: (x -> String, String -> Maybe x)) <- unsafeObjectGetClientData valueEntry
    ; case mValue of
        Nothing    -> return ()
        Just theValue -> set valueEntry [ text := present theValue ]
    }


class Observable w where
  change :: Event w (IO ())
  
instance Observable (TextCtrl a) where
  change = newEvent "change" (controlGetOnText) (controlOnText)



-- ObservableVar

-- add variable as WxObject
{-
type Observer x = (WxObject (), x -> IO ())

data ObservableVar x = ObservableVar (Var [Observer x]) (Var x)

instance Valued ObservableVar where
  value
    = newAttr "value" observableVarGetValue observableVarSetValue

mkObservableVar :: x -> IO (ObservableVar x)
mkObservableVar x =
 do { observersV <- variable [ value := [] ]
    ; var        <- variable [ value := x ]
    ; return $ ObservableVar observersV var
    }
    
observableVarGetValue :: ObservableVar x -> IO x
observableVarGetValue (ObservableVar _ var) = get var value

observableVarSetValue :: ObservableVar x -> x -> IO ()
observableVarSetValue (ObservableVar observersV var) x =
 do { myObservers <- get observersV value
    ; set var [ value := x ]
    ; sequence_ [ obs x | (_, obs) <- myObservers ]
    }

class Observable  x w | w -> x where
  observers :: Attr w [Observer x]

instance Observable x (ObservableVar x) where
  observers
    = newAttr "observers" observableVarGetObservers observableVarSetObservers

observableVarGetObservers :: ObservableVar x -> IO [Observer x]
observableVarGetObservers (ObservableVar observersV _) = get observersV value 

observableVarSetObservers :: ObservableVar x -> [Observer x] -> IO ()
observableVarSetObservers (ObservableVar observersV var) myObservers = -- return ()
 do { set observersV [ value := myObservers ]
    ; x <- get var value
    ; sequence_ [ obs x | (_, obs) <- myObservers ]
    }


-- all WxObjects get the event 'change'

class Observing w where
  change :: ObservableVar x -> Event w (x -> IO ())
  
instance Observing (WxObject a) where
  change observableVar
    = newEvent "change" (getOnObserve observableVar) (setOnObserve observableVar)

setOnObserve :: ObservableVar x -> Object a -> (x -> IO ()) -> IO ()
setOnObserve (ObservableVar observersV var) obj observer = 
 do { oldObservers <- get observersV value
    ; let otherObservers = filter ((/= objectCast obj) . fst) oldObservers
    ; set observersV [ value := (objectCast obj, observer) : otherObservers ]
    ; x <- get var value
    ; observer x
    }

getOnObserve :: ObservableVar x -> Object a -> IO (x -> IO ())
getOnObserve  (ObservableVar observersV _) obj =
 do { myObservers <- get observersV value
    ; case lookup (objectCast obj) myObservers of
        Just obs -> return obs
        Nothing  -> do { internalError "XTC" "getOnObserve" "object is not an observer" 
                       ; return $ \_ -> return ()
                       }
    }    
-}


-- Utility functions

safeIndex :: String -> [a] -> Int -> a
safeIndex msg xs i
    | i >= 0 && i < length xs = xs !! i
    | otherwise = internalError "XTC" "safeIndex" msg

internalError :: String -> String -> String -> a
internalError moduleName functionName errorString =
    error (moduleName ++ "." ++ functionName ++ ": " ++ errorString)


-- Test function

xtc :: IO ()
xtc = start $
 do { -- counterV <- mkObservableVar 1
    ; f <- frame []
    
    
    ; listV <- mkListView f [ tItems := ["sdfsdf", "fdssd"]
                               , enabled := True
                               ]
    
    ; choiceV <- mkChoiceView f [ tItems := ["sdfsdf", "fdssd"]
                               , enabled := True
                               ]
    ; comboV <- mkComboView f [ tItems := ["sdfsdf", "fdssd"]
                               , enabled := True
                               ]
    ; t <- textEntry f []
    ; ve <- mkValueEntry f [ tValue := Just True ]
  --  ; set t [ on (change counterV) := \i -> set t [ text := show i ] ] 
    
    ; bUp   <- button f [ text := "increase", on command := do { s1 <- get comboV tSelection
                                                               ; s2 <- get listV text
                                                               ; print (s1,s2)
                                                               } ] -- set counterV [ value :~ (+1) ] ]
  --  ; bDown <- button f [ text := "decrease", on command := set counterV [ value :~ (+ (-1::Int)) ] ]
    
 --   ; bChangeHandler <- button f [ text := "change handler"
 --                                , on command := set t [ on (change counterV) := \i -> set t [text := "<<"++show i++">>"] ]]
    ; set f [ layout := column 5 [ row 5 [ Graphics.UI.WX.label "Counter value:", widget t ]
   --                                      , hfloatCenter $ row 5 [ widget bUp, widget bDown ] 
   --                                      , hfloatCenter $ widget bChangeHandler
                                         , widget listV
                                         , widget choiceV
                                         , widget comboV
                                         , widget ve
                                         ]
                                 ]
    
    }