Merge branch 'master' of git.martlubbers.net:clean-tests
[clean-tests.git] / metaeditor / EditorExt.icl
diff --git a/metaeditor/EditorExt.icl b/metaeditor/EditorExt.icl
new file mode 100644 (file)
index 0000000..4512ae0
--- /dev/null
@@ -0,0 +1,150 @@
+implementation module EditorExt
+
+from Data.Func import $
+import Data.Tuple
+import qualified Data.Map as DM
+import StdTuple
+import Data.Maybe
+
+import iTasks
+import iTasks.UI.Editor.Common
+
+row :: String (Editor a) -> Editor a
+row l e = bijectEditorValue (tuple l) snd $
+       container2 label e <<@ directionAttr Horizontal
+
+import StdDebug, StdMisc
+
+// Choose combinators (Used for selecting constructors in ADTs)
+choose :: [(String, Editor a)] -> Editor (Int, a)
+choose [] = emptyEditor
+choose [(k,v)] = bijectEditorValue snd (tuple 0) $ row k v
+choose cs = {Editor|genUI=genUI,onEdit=onEdit,onRefresh=onRefresh}
+where
+       numConses = length cs
+       conses = map fst cs
+
+       genUI dp (consindex, value) vst=:{VSt|taskId,mode,optional,selectedConsIndex}
+               # cured = snd (cs !! consindex)
+               = case mode of
+                       Enter
+                               # (consChooseUI,consChooseMask) = genConsChooseUI taskId dp optional conses Nothing
+                               = (Ok (UI UIVarCons 'DM'.newMap [consChooseUI],CompoundMask {fields=[consChooseMask],state=JSONNull}),{vst & selectedConsIndex = selectedConsIndex})
+                       Update
+                               = case cured.Editor.genUI dp value vst of
+                                       (Ok (consUI=:(UI _ attr items), consMask=:(CompoundMask {fields})),vst)
+                                               # (consChooseUI,consChooseMask) = genConsChooseUI taskId dp optional conses (Just vst.selectedConsIndex)
+                                               = (Ok (UI UIVarCons attr [consChooseUI:items],CompoundMask {fields=[consChooseMask:fields],state=JSONNull})
+                                                       ,{vst & selectedConsIndex = selectedConsIndex})
+                                       (Error e,vst) = (Error e,vst)
+                       View
+                               = case cured.Editor.genUI dp value vst of
+                                       (Ok (consUI=:(UI _ attr items), consMask=:(CompoundMask {fields})),vst)
+                                               # (consViewUI,consViewMask) = genConsViewUI conses vst.selectedConsIndex
+                                               = (Ok (UI UIVarCons attr [consViewUI:items],CompoundMask {fields=[consViewMask:fields],state=JSONNull})
+                                                       ,{vst & selectedConsIndex = selectedConsIndex})
+                                       (Error e,vst) = (Error e,vst)
+
+       genConsChooseUI taskId dp optional gtd_conses mbSelectedCons = (consChooseUI,consChooseMask)
+       where
+               consOptions = [JSONObject [("id",JSONInt i),("text",JSONString gdc)] \\ gdc <- gtd_conses & i <- [0..]]
+               consChooseUI = uia UIDropdown (choiceAttrs taskId (editorId dp) (maybeToList mbSelectedCons) consOptions)
+               consChooseMask = FieldMask {touched=False,valid=optional || isJust mbSelectedCons,state=maybe JSONNull JSONInt mbSelectedCons}
+
+       genConsViewUI gtd_conses selectedCons
+               = (uia UITextView (valueAttr (JSONString (gtd_conses !! selectedCons))), newFieldMask)
+
+       //Update is a constructor switch
+       onEdit dp ([],JSONArray [JSONInt consIdx]) (i, val) (CompoundMask {fields=[FieldMask {FieldMask|touched,valid,state}:masks]}) vst//=:{VSt|mode} 
+               | not (trace_tn ("cons switch from " +++ toString i +++ " to " +++ toString consIdx)) = undef
+               # cured = snd (cs !! consIdx)
+               | consIdx < 0 || consIdx >= numConses
+                       = (Error "Constructor selection out of bounds", (i, val),vst)
+               //Create a default value for the selected constructor
+               //This is a rather ugly trick: We create a special target path that consists only of negative values that is
+               //decoded by the the onEdit instance of EITHER to create a value that consists of the correct nesting of LEFT's and RIGHT's
+               //                                                                              //TODO hier zit de bug
+       # (_,val,vst)   = cured.Editor.onEdit dp (consCreatePath consIdx numConses,JSONNull) val newCompoundMask vst
+               //Create a UI for the new constructor 
+               = case cured.Editor.genUI dp val {vst & mode = Enter} of
+                       (Ok (UI _ attr items, CompoundMask {fields=masks}),vst)
+                               //Construct a UI change that does the following: 
+                               //1: If necessary remove the fields of the previously selected constructor
+                               # removals = case state of
+//                                     (JSONInt prevConsIdx) = repeatn (length (snd (cs !! prevConsIdx))) (1,RemoveChild)
+//                                     (JSONInt prevConsIdx) = repeatn (length items) (1,RemoveChild)
+                                       _                 = []
+                               //2: Inserts the fields of the newly created ui
+                               # inserts = [(i,InsertChild ui) \\ ui <- items & i <- [1..]]
+                               # change = ChangeUI [] (removals ++ inserts)
+                               //Create a new mask for the constructor selection
+                               # consChooseMask = FieldMask {touched=True,valid=True,state=JSONInt consIdx}
+                               = (Ok (change,CompoundMask {fields=[consChooseMask:masks],state=JSONNull}), (consIdx, val), vst)
+                       (Error e,vst) = (Error e, (i, val), vst)
+
+       //Other events targeted directly at the ADT 
+       onEdit dp ([],e) (i, val) (CompoundMask {fields=[consChooseMask=:(FieldMask {FieldMask|touched,valid,state}):masks]}) vst=:{VSt|optional}
+               | e =: JSONNull || e =: (JSONArray []) // A null or an empty array are accepted as a reset events
+                       //If necessary remove the fields of the previously selected constructor
+                       # change = case state of
+//                             (JSONInt prevConsIdx) = ChangeUI [] (repeatn 32 /*(gtd_conses !! prevConsIdx).gcd_arity */(1,RemoveChild))
+                               _                     = NoChange                
+                       # consChooseMask = FieldMask {touched=True,valid=optional,state=JSONNull}
+                       = (Ok (change,CompoundMask {fields=[consChooseMask:masks],state=JSONNull}),(i, val), vst)       
+               = (Error "Unknown constructor select event ",(i, val),vst)
+
+       //Update is targeted somewhere inside this value
+       onEdit dp (tp,e) (i, val) mask=:(CompoundMask {fields,state}) vst 
+               # cured = snd (cs !! i)
+               //Adjust for the added constructor switch UI
+               # consChooseMask = hd fields
+               = case cured.Editor.onEdit dp (tp,e) val (CompoundMask {fields=tl fields,state=JSONNull}) vst of
+                       (Ok (change,CompoundMask {fields}),val,vst)
+                               # change = case change of
+                                       (ChangeUI attrChanges itemChanges) = ChangeUI attrChanges [(i + 1,c) \\ (i,c) <- itemChanges]
+                                       _                                  = NoChange
+                               = (Ok (change,CompoundMask {fields=[consChooseMask:fields],state=JSONNull}),(i, val),vst)
+                       (Error e,val,vst) = (Error e, (i, val), vst)
+
+       consCreatePath i n
+       | i >= n     = []
+       | n == 1     = []
+       | i < (n /2) = [ -1: consCreatePath i (n/2) ]
+       | otherwise  = [ -2: consCreatePath (i - (n/2)) (n - (n/2)) ]
+
+       onRefresh dp (i, new) (j, old) mask=:(CompoundMask {fields}) vst=:{VSt|mode,taskId,optional,selectedConsIndex=curSelectedConsIndex}
+               # cured = snd (cs !! i)
+               | numConses == 1
+                       # (change,val,vst) = cured.Editor.onRefresh dp new old mask vst
+                       = (change,(j, val),vst)
+               | otherwise
+                       //Adjust for the added constructor view/choose UI
+                       # consChooseMask = hd fields
+                       //Don't recursively refresh if no constructor has been chosen
+                       | (not mode =: View) && consChooseMask =: (FieldMask {FieldMask|state=JSONNull})
+                               = (Ok (NoChange,mask),(i, old),vst)
+                       = case cured.Editor.onRefresh dp new old (CompoundMask {fields=tl fields,state=JSONNull}) {vst & selectedConsIndex = 0} of
+                               (Ok (change,CompoundMask {fields}),val,vst=:{VSt|selectedConsIndex}) 
+                                       //If the cons was changed we need to update the selector
+                                       # consIndex = ~selectedConsIndex - 1
+                                       # consChange = if (selectedConsIndex < 0)
+                                               [(0, ChangeChild (ChangeUI [SetAttribute "value" (JSONArray [JSONInt consIndex,JSONBool True])] []))]
+                                               []
+                                       //Adjust the changes
+                                       # change = case change of
+                                               NoChange                                                     = if (consChange =: []) NoChange (ChangeUI [] consChange)
+                                               (ChangeUI attrChanges itemChanges)   = ChangeUI attrChanges (consChange ++ [(i + 1,c) \\ (i,c) <- itemChanges])
+                                               (ReplaceUI ui=:(UI type attr items))
+                                                       //Add the constructor selection/view ui
+                                                       # (consUI,_) = if (mode =: View) 
+                                                                                               (genConsViewUI conses consIndex)
+                                                                                               (genConsChooseUI taskId dp optional conses (Just consIndex))
+                                                       = ReplaceUI (UI type attr [consUI:items])
+                                       | otherwise
+                                               = (Ok (change, CompoundMask {fields=[consChooseMask:fields],state=JSONNull}), (j, val),{vst & selectedConsIndex = curSelectedConsIndex})
+
+                               (Ok (change,mask),val,vst=:{VSt|selectedConsIndex}) 
+                                       = (Error "Corrupt mask in generic choose editor",(i, old), vst)
+                               (Error e,val,vst) = (Error e,(i, val),vst)
+       onRefresh dp (i, new) (j, old) mask vst
+               = (Error "Corrupt mask in generic choose editor",(j, old), vst)