module SetHo.LookupTree
( ListViewElement(..)
, newLookupTreeview
) where
import Accessors.Dynamic
( DTree, DData(..), DConstructor(..), DSimpleEnum(..), DField(..)
, describeDField, sameDFieldType
, denumToString, denumToStringOrMsg, denumSetString
)
import Control.Monad ( void, when )
import qualified Data.Text as T
import Data.Tree ( Tree(..) )
import "gtk3" Graphics.UI.Gtk ( AttrOp( (:=) ) )
import qualified "gtk3" Graphics.UI.Gtk as Gtk
import System.Glib.Signals ( on )
import Text.Read ( readMaybe )
import Text.Printf ( printf )
data FieldElem =
FieldElem
{ feName :: Maybe String
, feUpstreamField :: DField
, feStagedField :: DField
} deriving Show
data ConstructorElem =
ConstructorElem
{ ceName :: Maybe String
, ceDName :: String
, ceCName :: String
} deriving (Show, Eq)
data SumElem =
SumElem
{ seName :: Maybe String
, seDName :: String
, seUpstreamSum :: DSimpleEnum
, seStagedSum :: DSimpleEnum
, seListStore :: Gtk.ListStore String
}
data ListViewElement =
LveField FieldElem
| LveConstructor ConstructorElem
| LveSum SumElem
ddataToTree :: Maybe String -> Either DField DData -> IO (Tree ListViewElement)
ddataToTree name (Left field) = return $ Node (LveField fe) []
where
fe =
FieldElem
{ feName = name
, feUpstreamField = field
, feStagedField = field
}
ddataToTree name (Right (DData dname (DConstructor cname fields))) = do
let ce =
ConstructorElem
{ ceName = name
, ceDName = dname
, ceCName = cname
}
children <- mapM (uncurry ddataToTree) fields
return $ Node (LveConstructor ce) children
ddataToTree name (Right (DData dname (DSum s))) = do
listStore <- Gtk.listStoreNew []
Gtk.treeModelSetColumn listStore (Gtk.makeColumnIdString 0) id
let se =
SumElem
{ seName = name
, seDName = dname
, seUpstreamSum = s
, seStagedSum = s
, seListStore = listStore
}
return $ Node (LveSum se) []
treeToStagedDData :: Tree ListViewElement -> Either DField DData
treeToStagedDData (Node (LveField fe) []) = Left (feStagedField fe)
treeToStagedDData (Node (LveField fe) _) =
error $ "treeToStagedDData: LveField " ++ show fe ++ " has children"
treeToStagedDData (Node (LveConstructor ce) fields) =
Right (DData dname (DConstructor cname (map f fields)))
where
dname = ceDName ce
cname = ceCName ce
getName :: Tree ListViewElement -> Maybe String
getName (Node (LveSum se) _) = seName se
getName (Node (LveConstructor ce') _) = ceName ce'
getName (Node (LveField fe) _) = feName fe
f x = (getName x, treeToStagedDData x)
treeToStagedDData (Node (LveSum se) []) =
Right (DData dname (DSum s))
where
dname = seDName se
s = seStagedSum se
treeToStagedDData (Node (LveSum _) _) =
error $ "treeToStagedDData: LveSum has children"
treeToUpstreamDData :: Tree ListViewElement -> Either DField DData
treeToUpstreamDData (Node (LveField fe) []) = Left (feUpstreamField fe)
treeToUpstreamDData (Node (LveField fe) _) =
error $ "treeToUpstreamDData: LveField " ++ show fe ++ " has children"
treeToUpstreamDData (Node (LveConstructor ce) fields) =
Right (DData dname (DConstructor cname (map f fields)))
where
dname = ceDName ce
cname = ceCName ce
getName :: Tree ListViewElement -> Maybe String
getName (Node (LveSum se) _) = seName se
getName (Node (LveConstructor ce') _) = ceName ce'
getName (Node (LveField fe) _) = feName fe
f x = (getName x, treeToUpstreamDData x)
treeToUpstreamDData (Node (LveSum se) []) =
Right (DData dname (DSum s))
where
dname = seDName se
s = seUpstreamSum se
treeToUpstreamDData (Node (LveSum _) _) =
error $ "treeToUpstreamDData: LveSum has children"
newLookupTreeview ::
String
-> DTree
-> IO Bool
-> (DTree -> IO ())
-> IO (Gtk.ScrolledWindow, IO DTree, DTree -> IO (), IO (), DTree -> IO ())
newLookupTreeview rootName initialValue getAutocommit commit = do
treeStore <- Gtk.treeStoreNew [] :: IO (Gtk.TreeStore ListViewElement)
treeview <- Gtk.treeViewNewWithModel treeStore :: IO Gtk.TreeView
Gtk.treeViewSetHeadersVisible treeview True
Gtk.treeViewSetEnableTreeLines treeview True
colName <- Gtk.treeViewColumnNew
colType <- Gtk.treeViewColumnNew
colUpstreamValue <- Gtk.treeViewColumnNew
colStagedValue <- Gtk.treeViewColumnNew
colCombo <- Gtk.treeViewColumnNew
Gtk.treeViewColumnSetTitle colName "name"
Gtk.treeViewColumnSetTitle colType "type"
Gtk.treeViewColumnSetTitle colUpstreamValue "upstream"
Gtk.treeViewColumnSetTitle colStagedValue "staged"
Gtk.treeViewColumnSetTitle colCombo "combo"
rendererName <- Gtk.cellRendererTextNew
rendererType <- Gtk.cellRendererTextNew
rendererStagedValue <- Gtk.cellRendererTextNew
rendererUpstreamValue <- Gtk.cellRendererTextNew
rendererCombo <- Gtk.cellRendererComboNew
Gtk.cellLayoutPackStart colName rendererName True
Gtk.cellLayoutPackStart colType rendererType True
Gtk.cellLayoutPackStart colUpstreamValue rendererUpstreamValue True
Gtk.cellLayoutPackStart colStagedValue rendererStagedValue True
Gtk.cellLayoutPackStart colCombo rendererCombo True
_ <- Gtk.treeViewAppendColumn treeview colName
_ <- Gtk.treeViewAppendColumn treeview colType
_ <- Gtk.treeViewAppendColumn treeview colUpstreamValue
_ <- Gtk.treeViewAppendColumn treeview colStagedValue
_ <- Gtk.treeViewAppendColumn treeview colCombo
let showName :: ListViewElement -> String
showName (LveSum se) = fromMName $ seName se
showName (LveField fe) = fromMName $ feName fe
showName (LveConstructor ce) = fromMName $ ceName ce
fromMName (Just r) = r
fromMName Nothing = "()"
Gtk.cellLayoutSetAttributes colName rendererName treeStore $
\lve -> [Gtk.cellText := showName lve]
let showType :: ListViewElement -> String
showType (LveSum se) = seDName se
showType (LveConstructor ce) = ceDName ce
showType (LveField fe) = describeDField (feStagedField fe)
Gtk.cellLayoutSetAttributes colType rendererType treeStore $
\lve -> [ Gtk.cellText := showType lve ]
let showField :: DField -> String
showField (DDouble x) = printf "%.2g" x
showField (DFloat x) = printf "%.2g" x
showField (DInt x) = show x
showField (DString x) = x
showField DSorry = ""
showSum :: DSimpleEnum -> String
showSum denum = case denumToString denum of
Left msg -> msg
Right r -> r
Gtk.cellLayoutSetAttributes colUpstreamValue rendererUpstreamValue treeStore $
\lve -> case lve of
LveField fe -> [ Gtk.cellText := showField (feUpstreamField fe)
, Gtk.cellTextEditable := False
]
LveSum se -> [ Gtk.cellText := showSum (seUpstreamSum se)
, Gtk.cellTextEditable := False
]
LveConstructor _ -> [ Gtk.cellText := ""
, Gtk.cellTextEditable := False
]
Gtk.cellLayoutSetAttributes colStagedValue rendererStagedValue treeStore $
\lve -> case lve of
LveField fe ->
[ Gtk.cellText := showField (feStagedField fe)
, Gtk.cellTextEditable := True
]
LveSum se ->
[ Gtk.cellText := showSum (seStagedSum se)
, Gtk.cellTextEditable := False
]
LveConstructor _ ->
[ Gtk.cellText := ""
, Gtk.cellTextEditable := False
]
Gtk.cellLayoutSetAttributes colCombo rendererCombo treeStore $ \lve ->
case lve of
LveSum (SumElem {seStagedSum = denum, seListStore = listStore}) ->
[ Gtk.cellComboHasEntry := False
, Gtk.cellTextEditable := True
, Gtk.cellComboTextModel := (listStore, Gtk.makeColumnIdString 0 :: Gtk.ColumnId String String)
, Gtk.cellText := denumToStringOrMsg denum
]
_ -> [ Gtk.cellMode := Gtk.CellRendererModeInert
, Gtk.cellText := ""
]
_ <- on rendererCombo Gtk.editingStarted $ \widget treePath -> do
lve <- Gtk.treeStoreGetValue treeStore treePath
case lve of
LveField _ -> error "Combo renderer is Field"
LveConstructor _ -> error "Combo renderer is Constructor"
LveSum se -> do
let comboBox = Gtk.castToComboBox widget
comboListStore <- Gtk.comboBoxSetModelText comboBox
let DSimpleEnum constructors active = seStagedSum se
mapM_ (Gtk.listStoreAppend comboListStore . T.pack) constructors
Gtk.comboBoxSetActive comboBox active
let mergeTrees :: Gtk.TreePath -> Tree ListViewElement -> IO ()
mergeTrees treePath newTree = do
moldTree <- Gtk.treeStoreLookup treeStore treePath
oldTree <- case moldTree of
Nothing -> error "failed looking up treestore"
Just r -> return r
:: IO (Tree ListViewElement)
let assertCompatible :: Bool -> IO ()
assertCompatible False = error "the \"impossible\" happened: trees aren't compatible"
assertCompatible True = return ()
case (oldTree, newTree) of
(Node (LveSum oldSum) [], Node (LveSum newSum) []) -> do
let (compatible, mergedSum) = mergeSums oldSum newSum
assertCompatible compatible
changed <- Gtk.treeStoreChange treeStore treePath (const (LveSum mergedSum))
case changed of
False -> error $ "merged sums didn't change"
True -> return ()
(_, Node (LveSum _) []) -> assertCompatible False
(_, Node (LveSum _) _) -> error "mergeTrees: new LveSum has children"
(Node (LveField oldField) [], Node (LveField newField) []) -> do
let (compatible, mergedField) = mergeFields oldField newField
assertCompatible compatible
changed <- Gtk.treeStoreChange treeStore treePath (const (LveField mergedField))
case changed of
False -> error $ "merged fields didn't change"
True -> return ()
(_, Node (LveField _) []) -> assertCompatible False
(_, Node (LveField _) _) -> error "mergeTrees: new LveField has children"
(Node (LveConstructor oldCons) oldChildren, Node (LveConstructor newCons) newChildren)
| oldCons /= newCons -> assertCompatible False
| length oldChildren /= length newChildren -> assertCompatible False
| otherwise -> do
mtreeIter <- Gtk.treeModelGetIter treeStore treePath
treeIter <- case mtreeIter of
Nothing -> error "error looking up tree iter"
Just r -> return r
nchildren <- Gtk.treeModelIterNChildren treeStore (Just treeIter)
void $ when (nchildren /= length oldChildren) $
error "error $ treeModelIterNChildren /= length oldChildren"
let mergeNthChild _ [] = return ()
mergeNthChild k (newChild:others) = do
mtreeIter' <- Gtk.treeModelGetIter treeStore treePath
treeIter' <- case mtreeIter' of
Nothing -> error "error looking up tree iter"
Just r -> return r
mchildIter <- Gtk.treeModelIterNthChild treeStore (Just treeIter') k
childIter <- case mchildIter of
Nothing -> error "treeModelIterNthChild failed"
Just r -> return r
mchildPath <- Gtk.treeModelGetPath treeStore childIter
childPath <- case mchildPath of
[] -> error "child TreePath is invalid"
r -> return r
mergeTrees childPath newChild
mergeNthChild (k + 1) others
mergeNthChild 0 newChildren
(_, Node (LveConstructor _) _) -> assertCompatible False
receiveNewUpstream :: DTree -> IO ()
receiveNewUpstream newMsg = do
moldTree <- Gtk.treeStoreLookup treeStore [0]
oldTree <- case moldTree of
Nothing -> error "failed looking up old treestore"
Just r -> return r
newTree <- ddataToTree (Just rootName) newMsg :: IO (Tree ListViewElement)
let (compatible, mergedTree) = compatibleTrees oldTree newTree
if compatible
then mergeTrees [0] newTree
else do
putStrLn "settings app rebuilding tree..."
Gtk.treeStoreClear treeStore
Gtk.treeStoreInsertTree treeStore [] 0 mergedTree
getLatestStaged = do
mtree <- Gtk.treeStoreLookup treeStore [0]
case mtree of
Nothing -> error "failed looking up treestore"
Just r -> return (treeToStagedDData r)
getLatestUpstream = do
mtree <- Gtk.treeStoreLookup treeStore [0]
return $ case mtree of
Nothing -> Nothing
Just r -> Just (treeToUpstreamDData r)
loadDTree :: DTree -> IO ()
loadDTree dtree = do
eupstream <- getLatestUpstream
Gtk.treeStoreClear treeStore
tree <- ddataToTree (Just rootName) dtree
Gtk.treeStoreInsertTree treeStore [] 0 tree
case eupstream of
Nothing -> return ()
Just upstream -> receiveNewUpstream upstream
takeLatestUpstream = do
let takeUpstreamElem :: ListViewElement -> ListViewElement
takeUpstreamElem (LveField fe) = LveField (fe {feStagedField = feUpstreamField fe})
takeUpstreamElem c@(LveConstructor _) = c
takeUpstreamElem (LveSum se) = LveSum (se {seStagedSum = seUpstreamSum se})
takeUpstream :: Gtk.TreeIter -> IO ()
takeUpstream iter = do
path <- Gtk.treeModelGetPath treeStore iter
_ <- Gtk.treeStoreChange treeStore path takeUpstreamElem
mchildren <- Gtk.treeModelIterChildren treeStore iter
case mchildren of
Just childIter -> takeUpstream childIter
Nothing -> return ()
msiblings <- Gtk.treeModelIterNext treeStore iter
case msiblings of
Just siblingIter -> takeUpstream siblingIter
Nothing -> return ()
miter <- Gtk.treeModelGetIterFirst treeStore
case miter of
Nothing -> return ()
Just root -> takeUpstream root
let sendDataIfAutocommitIsOn = do
autoCommit <- getAutocommit
when autoCommit (getLatestStaged >>= commit)
modifyField :: DField -> String -> DField
modifyField f0@(DDouble _) txt = case readMaybe txt of
Nothing -> f0
Just x -> DDouble x
modifyField f0@(DFloat _) txt = case readMaybe txt of
Nothing -> f0
Just x -> DFloat x
modifyField f0@(DInt _) txt = case readMaybe txt of
Nothing -> f0
Just x -> DInt x
modifyField (DString _) txt = DString txt
modifyField DSorry _ = DSorry
modifySum :: DSimpleEnum -> String -> DSimpleEnum
modifySum denum txt = case denumSetString denum txt of
Left _ -> denum
Right r -> r
_ <- on rendererStagedValue Gtk.edited $ \treePath txt -> do
lve0 <- Gtk.treeStoreGetValue treeStore treePath
let lve = case lve0 of
LveField fe -> LveField (fe {feStagedField = modifyField (feStagedField fe) txt})
LveSum se -> LveSum (se {seStagedSum = modifySum (seStagedSum se) txt})
ce@(LveConstructor _) -> ce
Gtk.treeStoreSetValue treeStore treePath lve
sendDataIfAutocommitIsOn
_ <- on rendererCombo Gtk.edited $ \treePath newVal -> do
lve0 <- Gtk.treeStoreGetValue treeStore treePath
let newLve = case lve0 of
LveSum se -> LveSum (se {seStagedSum = case denumSetString (seStagedSum se) newVal of
Left msg -> error $ "error updating sum elem: " ++ msg
Right r -> r
})
LveField _ -> error "cell renderer edited on Field"
LveConstructor _ -> error "cell renderer edited on Constructor"
Gtk.treeStoreSetValue treeStore treePath newLve
sendDataIfAutocommitIsOn
Gtk.treeStoreClear treeStore
tree <- ddataToTree (Just rootName) initialValue
Gtk.treeStoreInsertTree treeStore [] 0 tree
scroll <- Gtk.scrolledWindowNew Nothing Nothing
Gtk.containerAdd scroll treeview
Gtk.set scroll [ Gtk.scrolledWindowHscrollbarPolicy := Gtk.PolicyNever
, Gtk.scrolledWindowVscrollbarPolicy := Gtk.PolicyAutomatic
]
return (scroll, getLatestStaged, receiveNewUpstream, takeLatestUpstream, loadDTree)
mergeSums :: SumElem -> SumElem -> (Bool, SumElem)
mergeSums oldSum newSum
| seName oldSum /= seName newSum = (False, newSum)
| seDName oldSum /= seDName newSum = (False, newSum)
| oldOptions /= newOptions = (False, newSum)
| otherwise = (True, newSum {seStagedSum = seStagedSum oldSum})
where
DSimpleEnum oldOptions _ = seStagedSum oldSum
DSimpleEnum newOptions _ = seStagedSum newSum
mergeFields :: FieldElem -> FieldElem -> (Bool, FieldElem)
mergeFields oldElem newElem
| feName oldElem /= feName newElem = (False, newElem)
| not (sameDFieldType oldField newField) = (False, newElem)
| otherwise = (True, newElem {feStagedField = oldField})
where
oldField = feStagedField oldElem
newField = feStagedField newElem
compatibleTrees :: Tree ListViewElement -> Tree ListViewElement -> (Bool, Tree ListViewElement)
compatibleTrees (Node (LveSum oldSum) []) (Node (LveSum newSum) []) =
(compatible, Node (LveSum merged) [])
where
(compatible, merged) = mergeSums oldSum newSum
compatibleTrees _ newNode@(Node (LveSum _) []) = (False, newNode)
compatibleTrees _ (Node (LveSum _) _) = error "compatibleTrees: new LveSum has children"
compatibleTrees (Node (LveField oldField) []) (Node (LveField newField) []) =
(compatible, Node (LveField merged) [])
where
(compatible, merged) = mergeFields oldField newField
compatibleTrees _ newNode@(Node (LveField _) []) = (False, newNode)
compatibleTrees _ (Node (LveField _) _) = error "compatibleTrees: new LveField has children"
compatibleTrees (Node (LveConstructor oldCons) _) newNode@(Node (LveConstructor newCons) _)
| oldCons /= newCons = (False, newNode)
compatibleTrees (Node (LveConstructor _) oldChildren) (Node (LveConstructor newCons) newChildren) =
(childrenCompatible, Node (LveConstructor newCons) mergedChildren)
where
(childrenCompatible, mergedChildren) = mergeChildren newChildren
mergeChild :: Tree ListViewElement -> (Bool, Tree ListViewElement)
mergeChild newChild = tryOldChildren oldChildren
where
tryOldChildren [] = (False, newChild)
tryOldChildren (oldChild:others)
| compatible = (True, mergedChild)
| otherwise = tryOldChildren others
where
(compatible, mergedChild) = compatibleTrees oldChild newChild
mergeChildren :: [Tree ListViewElement] -> (Bool, [Tree ListViewElement])
mergeChildren (newChild:others) = (childCompatible && othersCompatible, mergedChild:mergedOthers)
where
(childCompatible, mergedChild) = mergeChild newChild
(othersCompatible, mergedOthers) = mergeChildren others
mergeChildren [] = (True, [])
compatibleTrees _ newNode@(Node (LveConstructor _) _) = (False, newNode)