From: Axel S. <A....@ke...> - 2005-11-26 10:57:08
|
On Fri, 2005-11-25 at 19:59 +0000, Duncan Coutts wrote: > On Wed, 2005-11-23 at 08:53 +0000, Axel Simon wrote: > > I thought I cc this to the list, since there's no need to discuss this > > in private. This thread just evolved about some CVS commit message. > > Here's another approach I've been experimenting with: > > Instead of creating a TreeStore or ListStore with one column in which we > store StablePtrs to Haskell values we could implement the TreeModel > interface itself in Haskell. I always thought of this as "level 2" of using trees. The problem I see with this approach is that there must be tons of stuff in TreeModel that we need to re-implement, even for the simplest list we want to show. Hence, if we always implement the whole TreeModel within Haskell then we need to provide some default implementation. If that's not too hard to do and efficient enough, then I'm all for it! > I've talked about this approach before but never spent the time to see > if it'd work. Well now I have and it looks something like this: > > data CustomStoreImpl iter = CustomStoreImpl { > customStoreGetFlags :: IO [TreeModelFlags], > customStoreGetNColumns :: IO Int, > customStoreGetColumnType :: Int -> IO GType, > customStoreGetIter :: TreePath -> IO (Maybe iter), > customStoreGetPath :: iter -> IO TreePath, > customStoreGetValue :: iter -> Int -> GValue -> IO (), > customStoreIterNext :: iter -> IO (Maybe iter), > customStoreIterChildren :: Maybe iter -> IO (Maybe iter), > customStoreIterHasChild :: iter -> IO Bool, > customStoreIterNChildren :: Maybe iter -> IO Int, > customStoreIterNthChild :: Maybe iter -> Int -> IO (Maybe iter), > customStoreIterParent :: iter -> IO (Maybe iter), > customStoreRefNode :: iter -> IO (), > customStoreUnrefNode :: iter -> IO () > } > > customStoreNew :: CustomStoreImpl iter -> IO CustomStore > customStoreNew impl = do > implPtr <- newStablePtr impl > makeNewGObject mkCustomStore $ > gtk2hs_store_new implPtr > > where CustomStore is an instance of TreeModel. > > Then we can implement a list model like so: > > getFlags = return [] > getNColumns = return 2 > getColumnType _ = return GType.string > getIter [n] = return (Just n) > getPath n = return [n] > getValue n 0 gvalue = do valueInit gvalue GType.string > valueSetString gvalue ("blah " ++ show n) > getValue n 1 gvalue = do valueInit gvalue GType.int > valueSetInt gvalue n > iterNext 5 = return Nothing > iterNext n = return (Just (n+1)) > iterChildren _ = return Nothing > iterHasChild _ = return False > iterNChildren _ = return 0 > iterNthChild _ _ = return Nothing > iterParent _ = return Nothing > refNode _ = return () > unrefNode _ = return () > > This works and allows me to connect it up to a TreeView with a couple > columns. Cool. Can you create a TreeModelSort from the Haskell model? > Of course this is the raw api exposed in Haskel. We would want to > present something simpler but it at least provides something concrete to > talk about. > > So the question is what kind of higher level API could we layer on top > of this kind of implementation and how would it compare to the > single-column List/TreeStore of StablePtrs approach. > > One advantage is that it would allow us to expose C types in the > TreeModel interface so it'd be possible to link it up to views other > than the TreeView. Yes, I guess that settles the issue for widgets like IconView. However, is the implementation of ListStore and TreeStore on top of TreeModel necessary? Don't you need to implement the five signals of the TreeModel to allow TreeView to interact with the model? > A disadvantage is that it is less obvious what the TreeModel type would > be. We may have to fall back to a runtime check when we connect a > renderer to a column in the model. A single run-time check when you connect is better than my approach where I would have to check that the tree model the TreeView was created with is the same as the TreeModel the Renderer was connected to. I would have to do this each time a value was requested. > An advantage of this method in general is that it should make it easier > to expose Haskell data structures as a TreeModel or have models that > generate data upon demand (eg a file system model). Yes, that's certainly attractive, especially when connecting to large databases. > I've been having a stab at what a higher level api might look like. > Here's an idea for a simple static model where we turn a list of Haskell > values and a list of conversion functions into a model with a number of > columns: > > class ColumnType v where > gtype :: v -> GType > setGValue :: v -> GValue -> IO () > > instance ColumnType String where > gtype _ = GType.string > toGValue v gvalue = do valueInit gvalue GType.string > valueSetString gvalue v > > -- and similar instances for Int, Bool etc > > data Column a = forall v. ColumnType v => Column (a -> v) Why can't we use data Column a = ColString (a -> String) | ColInt (a -> Int) ... > makeListStore :: [Column a] -> [a] -> CustomStore > > so we could use it like: > > data Phone = Phone { name :: String, number :: Int, marked :: Bool } > > makeListStore [Column name, Column number, Column marked] > [Phone { name = "me", number = "1234", marked = False } > ,Phone { name = "you", number = "5678", marked = True }] > > But I guess I'm still imagining that this produces a 'CustomStore' with > no type variable. So we don't have any way of statically enforcing the > types of renders match columns. If you want static type safety, the only choice is probably to restrict the user in the way the tree is built which is pretty much what I tried in the Mogul layer. What we could do here: nameRen <- textRendererNew numRen <- textRendererNew makeListStore [Column name [renText nameRen], Column (show . number) [renText numRen], Column (\p -> if marked p then "red" else "") [renBackground nameRen, renBackground numRen]] i.e. you pass the attributes of the renderers you want to connect as part of the column, which means that you have to create the renderers beforehand and that you can't change the renderers later on. I think this is bad since it prevents you from creating a new TreeView with new cell renderers after the store is created. Even with run-time type checks, the program will fall over the first time you show the widget, i.e. it should be easy to debug an incorrectly constructed table. > Basically this whole problem is because the Haskell type system doesn't > deal with record and extensible records very well. There are no first > class labels etc. I think this stuff could be solved by using HLists but > that's just too horrible to inflict upon anyone (and besides it uses > dozens of GHC type system extensions). Yes, there might be a solution somewhere. But if there is, it might be possible to implement on top of a run-time checked system. I'll rename my CVS tree to something else and assume that you do the whole model thing. I see what I can do about the makeGObject problem - that's more in my scope :-) Axel. |