From: Axel S. <A....@ke...> - 2005-11-23 08:55:28
|
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. On Tue, 2005-11-22 at 20:46 +0000, Duncan Coutts wrote: > On Mon, 2005-11-21 at 10:09 +0000, Axel Simon wrote: > > On Mon, 2005-11-21 at 09:48 +0000, Duncan Coutts wrote: > > > On Mon, 2005-11-21 at 08:51 +0000, Axel Simon wrote: > > > > On Sun, 2005-11-20 at 19:16 +0000, Duncan Coutts wrote: [..] > > > > > > What user api are we presenting? Does that api require that we implement > > > it by the single column stable ptr technique? > > > > Yes, I'm afraid, it will break the current API > > Oh that's ok. > > > and only allow a single > > column with an arbitrary Haskell data type. It would make things very > > Haskellish, though. Rough ideas (again): > > > > TreeModel: Has not type (TreeModel row) where row is the Haskell data > > type in the model. Say this is a record: > > > > data Phone = Phone { name :: String, number :: Integer, marked :: Bool } > > > > I'd say > > m <- treeModelNew -- of type (TreeModel Phone) > > v <- treeViewColumnNew -- of type TreeViewColumn > > cName <- cellRendererText > > cNumber <- cellRendererText > > > > Now here is where it gets speculative: > > > > cellLayoutAddRenderer cName [attrText m := name, > > attrBackground m := \p -> if marked p then "red" else "white"] > > cellLayoutAddRenderer cNumber [attrText m := (show . number) ] > > > > So attrText is one of the attributes of CellRendererText: > > > > attrText :: TreeModelClass model => CellRendererText -> model row -> > > (row -> String) -> ... > > > > The only reason the attrText takes the model is to get the type right. > > That actually doesn't mean it's type correct: I can pass a different > > model to attrText than what I install into the final TreeView. However, > > I can check that the models are the same when I actually set the > > attributes. > > Perhaps we should parameterise the renderer or column or something by > the model type. So we can guarantee that the types for the renderers > match the model. I thought about that, but TreeViewColumn is already a widget, and having widgets with a type parameter is not a very good idea, IMHO, mainly because it messes up our type generation, casting, container functions, etc. I did exactly this for the GtkCList back in the days of gtk+hs and Manuel wasn't happy about that (which I can now understand). Mainly because it would probably mean that TreeView, ComboBox, IconView, etc. all get a type parameter. > > I hope this is understandable. > > Yes. > > It's a nice idea. The only downside is having to implement it as a > single column model which will make it not work for as the model for > CellView/ComoBoxEntry/etc. I think it is an omission Gtk that this won't work. Once I figure out what I need, I will file a bug report. If it gets incorporated, then we can use some private (uh-uh) header files to access the underlying functions for earlier Gtk+ versions. > Of course the advantage is that arbitrary Haskell types can be used. I thought about whether there is a way to actually mix the two models, but I'm not sure it's possible. On creation of the model, we could always enforce that column 0 is the Haskell type and have the Combo box on creation always add a String column with number 1, an IconView a String and a GdkPixbuf for column 1 and 2, etc. It might work, but it is a bit clumsy. Axel. |
From: Duncan C. <dun...@wo...> - 2005-11-25 19:58:56
|
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'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. 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. 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. 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). 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) 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. Though at least those run time checks would only be once at the time when the renderers are connected to the model and view. I will go look at the various Haskell DB libs for more inspiration. 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). Duncan |
From: Duncan C. <dun...@wo...> - 2005-11-25 22:01:46
Attachments:
Screenshot-listtest.png
ListTest.hs
|
On Fri, 2005-11-25 at 19:59 +0000, Duncan Coutts wrote: > 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 }] So attached is an implementation of this and a screenshot of it running. You won't be able to compile it yourself because it uses some C glue for the customStoreNew stuff, but what I want to demo is the implementation of makeListStore :: [Column a] -> [a] -> CustomStoreImpl Int The implementation could be made a tad faster by internally converting the list of column functions and the list of rows into arrays. I expect another implementation would not be too hard: makeTreeStore :: [Column a] -> Tree a -> CustomStoreImpl [Int] So we could take the approach of not using the default GtkTreeStore and GtkListStore but using the Haskell implementations instead. I looked at the HaskellDB papers. The original one uses TREX which is defunct. The later paper uses a fairly complex system - though not quite as complex as the HList stuff. I'm not inspired by either. Duncan |
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. |
From: Duncan C. <dun...@wo...> - 2005-11-26 14:54:32
|
On Sat, 2005-11-26 at 10:54 +0000, Axel Simon wrote: > I see what I can do about the makeGObject problem - > that's more in my scope :-) Heh, just to make that problem more complex: http://cvs.gnome.org/viewcvs/glib/gobject/gobject.c?rev=1.74&view=markup They've moved the floating concept down to the GObject level and make the GtkObject floating thing use that. So I'm not quite sure how that changes the memory management rules. I hope it will all become clear. Of course we don't have to worry about that until Gtk+ 2.10 which will not be for another 6 months or more. Duncan |
From: Duncan C. <dun...@wo...> - 2005-11-26 11:58:01
|
On Sat, 2005-11-26 at 10:54 +0000, Axel Simon wrote: > 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. It's rather the other way around. For the simplest static list it's easy. I've already done that bit. > 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! Notifying the view of changes in the model involves calling: gtk_tree_model_row_changed gtk_tree_model_row_inserted gtk_tree_model_row_has_child_toggled gtk_tree_model_row_deleted gtk_tree_model_rows_reordered which emit the corresponding signals which notifies the view(s). So we'd want the Haskell layer to present functions for modifying a store to emit the right signals. There is also more work to implement the drag & drop and sortable interfaces. There is one difficulty in the implementation which is a memory management issue. The GtkTreeIter values are just stack allocated values and so we cannot know when they are no longer used, so we can't put references to Haskell values (rg StablePtrs) into them, or if we do, we must remember them so we can free them later. I'm still trying to figure out the best way of dealing with this. > > 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? The TreeModelSort is just another interface so the same technique would work. I just implement a GObject in C that implements that interface and delegate the implementation of each method to Haskell land. This is what the C glue layer I've already got does: struct _Gtk2HsStore { GObject parent; /*< private >*/ HsStablePtr impl; /* a StablePtr CustomStoreImpl */ }; and for example: static void gtk2hs_store_get_value (GtkTreeModel *tree_model, GtkTreeIter *iter, gint column, GValue *value) { Gtk2HsStore *store = (Gtk2HsStore *) tree_model; gtk2hs_store_get_value_impl(store->impl, iter->user_data, column, value); } where gtk2hs_store_get_value_impl is an exported Haskell function: foreign export ccall "gtk2hs_store_get_value_impl" customStoreGetValue_static :: StablePtr (CustomStoreImpl iter) -> StablePtr iter -> CInt -> Ptr GValue -> IO () customStoreGetValue_static :: StablePtr (CustomStoreImpl iter) -> StablePtr iter -> CInt -> Ptr GValue -> IO () customStoreGetValue_static storePtr iterPtr column gvaluePtr = do store <- deRefStablePtr storePtr iter <- deRefStablePtr iterPtr customStoreGetValue store iter (fromIntegral column) (GValue gvaluePtr) > > 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. Though apparently that only works for trees, for lists the GtkTreeView still queries the whole list. Though apparently it might not do that if the "fixed height mode" is turned on. But it at least would work for directory trees. Where it is really important not to recursively scan a whole directory hierarchy. > > data Column a = forall v. ColumnType v => Column (a -> v) > > Why can't we use > > data Column a = ColString (a -> String) > | ColInt (a -> Int) > ... Yeah, fair enough. The types of columns is actually quite limited. And that means it's Haskell98 too. (Yet again I'm too quick to jump into using type classes and forall types) > > 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. Yes, it should be ok to make the columns and renderers with the view without any reference to the store. And then one set of runtime checks is needed when the store is connected to the view+renderers. I think the construction of the view+cols+renderers could be more declarative than it is at the moment. For example we don't necessarily need to expose the fact that renderers are objects in their own right. > > 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. True. So that's probably what we should aim for - a simple runtime check - especially if we can limit it to a runtime check when the view/model are constructed/connected. Then we can think about more cunning types later. > I'll rename my CVS tree to something else and assume that you do the > whole model thing. Don't abandon me! :-) I don't really know yet what the higher level api should look like, and you seem to have some good ideas about that. > I see what I can do about the makeGObject problem - > that's more in my scope :-) Oh yes, that problem. It's not quite as interesting is it. Duncan |
From: Axel S. <A....@ke...> - 2005-11-26 12:45:18
|
On Sat, 2005-11-26 at 11:58 +0000, Duncan Coutts wrote: > > 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! > > Notifying the view of changes in the model involves calling: > gtk_tree_model_row_changed > gtk_tree_model_row_inserted > gtk_tree_model_row_has_child_toggled > gtk_tree_model_row_deleted > gtk_tree_model_rows_reordered > > which emit the corresponding signals which notifies the view(s). > So we'd want the Haskell layer to present functions for modifying a > store to emit the right signals. Ok, so these C functions will trigger the signals (i.e. you can copy'n'paste the code from gtktreemodel.c?). > There is also more work to implement the drag & drop and sortable > interfaces. Just the interfaces, right? I.e. we "only" need to figure out how these interfaces work in Gtk and then reserve the right slots in the gtk2hs_tree_model? I'm certainly not in favour of re-implementing anything like TreeModelSort or even the DND stuff. > There is one difficulty in the implementation which is a memory > management issue. The GtkTreeIter values are just stack allocated values > and so we cannot know when they are no longer used, so we can't put > references to Haskell values (rg StablePtrs) into them, or if we do, we > must remember them so we can free them later. I'm still trying to figure > out the best way of dealing with this. I suppose the TreeIter should really only contain an integer index into the Haskell data structure. The way it could work is that we find some sort of enumeration of the underlying data structure. For a plain list implemented by an array it would just be the array index. For a tree constructed by an list of lists, it could be the element position of the flattended list. The disadvantage of this scheme is that we need to force the user to make his/her data structure an instance of class Ix, which is not always trivial. This only works since TreeIters are invalid once the model changes. > > > > 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? > > The TreeModelSort is just another interface so the same technique would > work. I just implement a GObject in C that implements that interface and > delegate the implementation of each method to Haskell land. But (see above) I'd rather not re-implement TreeModelSort. TreeModelSort basically wraps all calls to the underlying TreeModel and does a permuation on all TreeIters/TreePaths such that the TreeModel underneath looks sorted. We shouldn't have to do that in Haskell. > This is what the C glue layer I've already got does: > > struct _Gtk2HsStore > { > GObject parent; > > /*< private >*/ > HsStablePtr impl; /* a StablePtr CustomStoreImpl */ > }; That's it? I suppose you've defined the klass thing elsewhere. > > 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. > > Yes, it should be ok to make the columns and renderers with the view > without any reference to the store. And then one set of runtime checks > is needed when the store is connected to the view+renderers. > > I think the construction of the view+cols+renderers could be more > declarative than it is at the moment. For example we don't necessarily > need to expose the fact that renderers are objects in their own right. Sure, but people want to do the weirdest stuff, for example, they might want to add a new renderer to a column later on. That's when things with good intentions like Mogul fall over. > True. So that's probably what we should aim for - a simple runtime check > - especially if we can limit it to a runtime check when the view/model > are constructed/connected. Then we can think about more cunning types > later. > > > I'll rename my CVS tree to something else and assume that you do the > > whole model thing. > > Don't abandon me! :-) I'll try not to. But you've started to code in C...:-) > I don't really know yet what the higher level api should look like, and > you seem to have some good ideas about that. I'm not so sure about the latter. It's nice if you get it working though. Axel. |
From: Duncan C. <dun...@wo...> - 2005-11-26 17:19:09
|
On Sat, 2005-11-26 at 12:43 +0000, Axel Simon wrote: > On Sat, 2005-11-26 at 11:58 +0000, Duncan Coutts wrote: > > There is one difficulty in the implementation which is a memory > > management issue. The GtkTreeIter values are just stack allocated values > > and so we cannot know when they are no longer used, so we can't put > > references to Haskell values (rg StablePtrs) into them, or if we do, we > > must remember them so we can free them later. I'm still trying to figure > > out the best way of dealing with this. > > I suppose the TreeIter should really only contain an integer index into > the Haskell data structure. That is exactly what Owen Taylor suggested: http://mail.gnome.org/archives/language-bindings/2005-November/msg00004.html > The way it could work is that we find some sort of enumeration of the > underlying data structure. For a plain list implemented by an array it > would just be the array index. Yep. > For a tree constructed by an list of lists, it could be the element > position of the flattended list. The disadvantage of this scheme is > that we need to force the user to make his/her data structure an > instance of class Ix, which is not always trivial. Yeah the indexing for trees is not so easy. Doing a depth first indexing scheme would negate the advantages of calculating the tree on demand (eg think of a very deep directory structure). We actually have 32 * 3 bits to play with and possibly 32 more if we don't think we need the stamp: struct GtkTreeIter { gint stamp; gpointer user_data; gpointer user_data2; gpointer user_data3; }; One idea for an indexing system goes like this: Use bits to denote a position in the tree by binary search at each level in the tree. For example if you have 8 items in the top level of the tree then you can index them with 3 bits. Then you move to the next level of the tree and use more bits. It does put a lower maximum depth of the tree than the depth first (flattening) indexing but it doesn't require the whole of the tree to be evaluated or traversed to find the item. So it gives log time lookup. Eg suppose we had a tree with 8 items at every level in the tree, then we have a maximum depth of 32 (= 32 * 3 / log_2 8). Actually it's slightly worse than that because you need to distinguish when a index stops. That is you need to distinguish stopping at the node itself or going on to it's children. That can be done by calling the node itself the 0'th index of it's children. So instead of getting 8 children into 3 bits you can only have 7 because the 0th one means stopping at the parent node. Or something like that. I'm not sure if that's a reasonable limitation of the indexing. I suppose I could find out by seeing what is the maximum number of bits I need to index every file on my hard drive. > This only works since TreeIters are invalid once the model changes. Yes, that is fine. We do not need to support persistent iterators. Duncan |
From: Duncan C. <dun...@wo...> - 2005-11-26 21:11:10
|
On Sat, 2005-11-26 at 17:19 +0000, Duncan Coutts wrote: > I'm not sure if that's a reasonable limitation of the indexing. I > suppose I could find out by seeing what is the maximum number of bits I > need to index every file on my hard drive. After writing a 30 line program, the answer is... 78 bits. That's 40GB and 1.2 million files. That's probably a reasonably large tree compared to the size of trees we expect to be able to display in a TreeView. We've got 96 bits and if necessary we could probably steal up to 16 bits from the 32 bit stamp field. The main weakness is in the depth of the tree. A very deep but narrow tree will use up the bits more quickly than most. Mind you, in practise, very deep trees do not make a good user interface. Duncan |
From: Axel S. <A....@ke...> - 2005-11-27 10:31:27
|
On Sat, 2005-11-26 at 17:19 +0000, Duncan Coutts wrote: > On Sat, 2005-11-26 at 12:43 +0000, Axel Simon wrote: > > On Sat, 2005-11-26 at 11:58 +0000, Duncan Coutts wrote: > > > > There is one difficulty in the implementation which is a memory > > > management issue. The GtkTreeIter values are just stack allocated values > > > and so we cannot know when they are no longer used, so we can't put > > > references to Haskell values (rg StablePtrs) into them, or if we do, we > > > must remember them so we can free them later. I'm still trying to figure > > > out the best way of dealing with this. > > > > I suppose the TreeIter should really only contain an integer index into > > the Haskell data structure. > > That is exactly what Owen Taylor suggested: > http://mail.gnome.org/archives/language-bindings/2005-November/msg00004.html > > > The way it could work is that we find some sort of enumeration of the > > underlying data structure. For a plain list implemented by an array it > > would just be the array index. > > Yep. > > > For a tree constructed by an list of lists, it could be the element > > position of the flattended list. The disadvantage of this scheme is > > that we need to force the user to make his/her data structure an > > instance of class Ix, which is not always trivial. > > Yeah the indexing for trees is not so easy. Doing a depth first indexing > scheme would negate the advantages of calculating the tree on demand (eg > think of a very deep directory structure). Ok, I can see that. > We actually have 32 * 3 bits to play with and possibly 32 more if we > don't think we need the stamp: > > struct GtkTreeIter > { > gint stamp; > gpointer user_data; > gpointer user_data2; > gpointer user_data3; > }; > > One idea for an indexing system goes like this: > > Use bits to denote a position in the tree by binary search at each level > in the tree. For example if you have 8 items in the top level of the > tree then you can index them with 3 bits. Then you move to the next > level of the tree and use more bits. > > It does put a lower maximum depth of the tree than the depth first > (flattening) indexing but it doesn't require the whole of the tree to be > evaluated or traversed to find the item. So it gives log time lookup. > > Eg suppose we had a tree with 8 items at every level in the tree, then > we have a maximum depth of 32 (= 32 * 3 / log_2 8). > > Actually it's slightly worse than that because you need to distinguish > when a index stops. That is you need to distinguish stopping at the node > itself or going on to it's children. That can be done by calling the > node itself the 0'th index of it's children. So instead of getting 8 > children into 3 bits you can only have 7 because the 0th one means > stopping at the parent node. Or something like that. > > I'm not sure if that's a reasonable limitation of the indexing. I > suppose I could find out by seeing what is the maximum number of bits I > need to index every file on my hard drive. I think this might bite some users who have, say 2-levels only and zillions of entries under each. Maybe we represent a TreeIter as a record of three integers and leave the implementation up to the model implementor. As a default for trees, I would suggest the following: We keep an array of entries for all nodes with the same prefix (in the TreePath sense). For example, given the tree with the indices: 0 0.0 0.1 0.2 0.2.0 0.2.1 0.3 1 1.0 1.1 1.2 we'd have four arrays: prefix = [], elements = [0, 1] prefix = [0], elements = [0.0, 0.1, 0.2, 0.3] prefix = [0,2], elements = [0.2.0, 0.2.1] prefix = [1], element = [1.0, 1.1, 1.2] Assuming that each row has the same type, we can type-safely create an array of arrays that contain these elements. To iterate over these, we only need to fields of the TreeIter: One for the outer array number (call it y), one for the inner (call it x). Implementing treeModelIterNext is trivial, since it only needs to increment the x field and check if it's still a valid entry. Implementing treeModelIterChildren and the like requires a search through the outer array if the current element has children. But that should be quick, we could even use a hashtable that maps prefixes to a y indices. We could even efficiently implement a normal list with this concept. Axel. |
From: Duncan C. <dun...@wo...> - 2005-11-27 10:43:17
|
On Sun, 2005-11-27 at 10:29 +0000, Axel Simon wrote: > > I'm not sure if that's a reasonable limitation of the indexing. I > > suppose I could find out by seeing what is the maximum number of bits I > > need to index every file on my hard drive. > > I think this might bite some users who have, say 2-levels only and > zillions of entries under each. I think that'd be fine. We've got at least 96 bits remember. So we can even have a three level tree with 2^32-1 elements at each level. I'll have a think about the other scheme you suggested. Duncan |
From: Duncan C. <dun...@wo...> - 2005-11-28 10:49:51
|
On Sun, 2005-11-27 at 10:29 +0000, Axel Simon wrote: > Maybe we represent a TreeIter as a record of three integers and leave > the implementation up to the model implementor. As a default for > trees, I would suggest the following: > > We keep an array of entries for all nodes with the same prefix (in the > TreePath sense). For example, given the tree with the indices: > 0 > 0.0 > 0.1 > 0.2 > 0.2.0 > 0.2.1 > 0.3 > 1 > 1.0 > 1.1 > 1.2 > > we'd have four arrays: > prefix = [], elements = [0, 1] > prefix = [0], elements = [0.0, 0.1, 0.2, 0.3] > prefix = [0,2], elements = [0.2.0, 0.2.1] > prefix = [1], element = [1.0, 1.1, 1.2] > Assuming that each row has the same type, we can type-safely create an > array of arrays that contain these elements. To iterate over these, we > only need to fields of the TreeIter: One for the outer array number > (call it y), one for the inner (call it x). So this means we need axillary storage proportional to the size of the tree right? And we have to construct these arrays on demand as we explore the tree (so as not to force the evaluation of the whole tree initially). > Implementing treeModelIterNext is trivial, since it only needs to > increment the x field and check if it's still a valid entry. Checking if it's a valid entry takes log time doesn't it? Unless instead of (or perhaps in addition to) the prefix & element arrays containing indicies they contained the elements themselves. I think the zipper works that way, it uses a path of elements rather than a path of offsets. > Implementing treeModelIterChildren and the like requires a search > through the outer array if the current element has children. But that > should be quick, I was thinking it'd be O(1) since you'd just lookup the y in the array and then see if it has any children but actually it'd be O(log) because we represent it by an entry for it's parent and an index below that. Since each interior node is represented in the array twice, once as a child and once as a parent. > we could even use a hashtable that maps prefixes to a y > indices. Duncan |
From: Axel S. <A....@ke...> - 2005-11-28 11:27:11
|
Duncan, I'm a bit lost. On Mon, 2005-11-28 at 10:50 +0000, Duncan Coutts wrote: > On Sun, 2005-11-27 at 10:29 +0000, Axel Simon wrote: > > > Maybe we represent a TreeIter as a record of three integers and leave > > the implementation up to the model implementor. As a default for > > trees, I would suggest the following: > > > > We keep an array of entries for all nodes with the same prefix (in the > > TreePath sense). For example, given the tree with the indices: > > 0 > > 0.0 > > 0.1 > > 0.2 > > 0.2.0 > > 0.2.1 > > 0.3 > > 1 > > 1.0 > > 1.1 > > 1.2 > > > > we'd have four arrays: > > prefix = [], elements = [0, 1] > > prefix = [0], elements = [0.0, 0.1, 0.2, 0.3] > > prefix = [0,2], elements = [0.2.0, 0.2.1] > > prefix = [1], element = [1.0, 1.1, 1.2] Ok, let's call this array with the four elements the "prefix array" as you did below. > > Assuming that each row has the same type, we can type-safely create an > > array of arrays that contain these elements. To iterate over these, we > > only need to fields of the TreeIter: One for the outer array number > > (call it y), one for the inner (call it x). > > So this means we need axillary storage proportional to the size of the > tree right? And we have to construct these arrays on demand as we > explore the tree (so as not to force the evaluation of the whole tree > initially). Well, this was meant as an example implementation of a TreeModel. So when I wrote "element = [1.0, 1.1, 1.2]" I actually meant that the "1.0" stands for the data of element 1.0. Afterall, we need to actually store the data somewhere. So what I proposed was not just an indexing scheme but rather a way to implement the data base. If you'd want to implement an on-demand model, then you would have to insert and destroy these arrays on the fly. > > Implementing treeModelIterNext is trivial, since it only needs to > > increment the x field and check if it's still a valid entry. > > Checking if it's a valid entry takes log time doesn't it? Unless instead > of (or perhaps in addition to) the prefix & element arrays containing > indicies they contained the elements themselves. See above. Even if "element" is a plain list, you could store the length of the list separately and then test in O(1) if the next iterator exists. > I think the zipper > works that way, it uses a path of elements rather than a path of > offsets. Which zipper?! > > Implementing treeModelIterChildren and the like requires a search > > through the outer array if the current element has children. But that > > should be quick, > > I was thinking it'd be O(1) since you'd just lookup the y in the array > and then see if it has any children but actually it'd be O(log) because > we represent it by an entry for it's parent and an index below that. The function treeModelIterChildren should give me the following: input output complexity 0.1 0.2 (a) 0.2 0.2.0 (b) 0.2.0 0.2.1 (c) 0.2.1 0.3 (d) 0.3 1 (e) (a) lookup 0.1.0 in hash table, not there, increment last, check if 0.2 is larger than the length of the current array, no, ok (b) lookup 0.2.0 in hash table, found (c) like (a) (d) like (a), but 0.2.2 is out of bounds, lookup 0.3 in hash table, found (e) like (d), but 0.4 not in hash table, lookup 1 in hash table, found In summary, you might need several lookups in a hash table, but it's at most three times (child, sibling, ancestor), unless we can have an incomplete tree (i.e. 0.2 followed by 1.1.1.1, but I don't think that's allowed). > Since each interior node is represented in the array twice, once as a > child and once as a parent. I'm confused here. Each element is represented exactly once in my model. Again, my suggestion is a concrete implementation of a TreeModel which makes do with two integers for indexing. You could do it differently and maybe even have your indexing scheme rather than the two integers. I merely want to point out that you don't think you need such an complex indexing scheme. Maybe you can think of an example implementation with your indexing scheme, so that I can see that it has advantages over this one-array-per-level approach. Axel. |
From: Duncan C. <dun...@wo...> - 2005-12-07 12:22:16
Attachments:
Screenshot-listtest.png
|
On Sun, 2005-11-27 at 10:59 +0000, Axel Simon wrote: > Ok, I reread your proposal. I thought you'd fix a number of bits you > choose to represent a particular level; but you're actually saying you > choose the number of bits you use per level by the number of nodes you > currently have on that level. Right. > I don't think there are any concerns with regards to the number of nodes > that can be addressed: Instead of asking how many nodes you can store, > you can ask how much fragmentation you get through this indexing. You > loose half the possible elements (worst case) for each level (i.e. 8 > element on one level need to be addressed by 4 bits, hence you loose 7 > indices due to fragmentation). Hence the extreme case is 96 levels with > one or zero elements. Indeed. > On the other hand, I think it is quite complicated to implement > (shifting 96 bits at a time?). Here's a couple functions for getting and setting integers of 'count' bits at offset 'off' into the 96 bits of the Iter. getBitSlice :: Iter -> Int -> Int -> Word32 getBitSlice (Iter a b c) off count = getBitSliceWord32 a off count .|. getBitSliceWord32 b (off-32) count .|. getBitSliceWord32 c (off-64) count where getBitSliceWord32 :: Word32 -> Int -> Int -> Word32 getBitSliceWord32 word off count = word `shiftR` off .&. (1 `shiftL` count - 1) setBitSlice :: Iter -> Int -> Int -> Word32 -> Iter setBitSlice (Iter a b c) off count value = assert (value < 1 `shiftL` count) $ Iter (setBitSliceWord32 a off count value) (setBitSliceWord32 b (off-32) count value) (setBitSliceWord32 c (off-64) count value) where setBitSliceWord32 :: Word32 -> Int -> Int -> Word32 -> Word32 setBitSliceWord32 word off count value = let mask = (1 `shiftL` count - 1) `shiftL` off in (word .&. complement mask) .|. (value `shiftL` off) > To advertise my suggestion... Advancing iterators is O(1) in all cases, > even if you iter through the children (which requires a hashtable > lookup). And it is a bit simpler to implement. That sounds good! > That said, we could have both approaches. I've mostly implemented (but not fully tested) the above approach. (See the attached screenshot.) I might have a go with your suggestion and see how they compare. Duncan |
From: Axel S. <A....@ke...> - 2005-12-07 13:08:47
|
On Wed, 2005-12-07 at 12:22 +0000, Duncan Coutts wrote: > On Sun, 2005-11-27 at 10:59 +0000, Axel Simon wrote: > Here's a couple functions for getting and setting integers of 'count' > bits at offset 'off' into the 96 bits of the Iter. > > getBitSlice :: Iter -> Int -> Int -> Word32 > getBitSlice (Iter a b c) off count = > getBitSliceWord32 a off count > .|. getBitSliceWord32 b (off-32) count > .|. getBitSliceWord32 c (off-64) count > > where getBitSliceWord32 :: Word32 -> Int -> Int -> Word32 > getBitSliceWord32 word off count = > word `shiftR` off .&. (1 `shiftL` count - 1) > > setBitSlice :: Iter -> Int -> Int -> Word32 -> Iter > setBitSlice (Iter a b c) off count value = > assert (value < 1 `shiftL` count) $ > Iter (setBitSliceWord32 a off count value) > (setBitSliceWord32 b (off-32) count value) > (setBitSliceWord32 c (off-64) count value) > > where setBitSliceWord32 :: Word32 -> Int -> Int -> Word32 -> Word32 > setBitSliceWord32 word off count value = > let mask = (1 `shiftL` count - 1) `shiftL` off > in (word .&. complement mask) .|. (value `shiftL` off) Ok, so you can do it :-) > > To advertise my suggestion... Advancing iterators is O(1) in all cases, > > even if you iter through the children (which requires a hashtable > > lookup). And it is a bit simpler to implement. > > That sounds good! > > > That said, we could have both approaches. > > I've mostly implemented (but not fully tested) the above approach. (See > the attached screenshot.) I might have a go with your suggestion and see > how they compare. Maybe you can commit something, so I can look at it? No rush, though, I'm supposed to work on my PhD (and spent the whole morning adding the onInsertText signal). Axel. |
From: Duncan C. <dun...@wo...> - 2005-12-07 13:57:22
|
On Wed, 2005-12-07 at 13:06 +0000, Axel Simon wrote: > On Wed, 2005-12-07 at 12:22 +0000, Duncan Coutts wrote: > > On Sun, 2005-11-27 at 10:59 +0000, Axel Simon wrote: > > > Here's a couple functions for getting and setting integers of 'count' > > bits at offset 'off' into the 96 bits of the Iter. > > > > getBitSlice :: Iter -> Int -> Int -> Word32 > > getBitSlice (Iter a b c) off count = > > getBitSliceWord32 a off count > > .|. getBitSliceWord32 b (off-32) count > > .|. getBitSliceWord32 c (off-64) count > > > > where getBitSliceWord32 :: Word32 -> Int -> Int -> Word32 > > getBitSliceWord32 word off count = > > word `shiftR` off .&. (1 `shiftL` count - 1) > > > > setBitSlice :: Iter -> Int -> Int -> Word32 -> Iter > > setBitSlice (Iter a b c) off count value = > > assert (value < 1 `shiftL` count) $ > > Iter (setBitSliceWord32 a off count value) > > (setBitSliceWord32 b (off-32) count value) > > (setBitSliceWord32 c (off-64) count value) > > > > where setBitSliceWord32 :: Word32 -> Int -> Int -> Word32 -> Word32 > > setBitSliceWord32 word off count value = > > let mask = (1 `shiftL` count - 1) `shiftL` off > > in (word .&. complement mask) .|. (value `shiftL` off) > > Ok, so you can do it :-) Yeah, ok that was overkill. :-) I could have just said "actually it's not too hard". > > > To advertise my suggestion... Advancing iterators is O(1) in all cases, > > > even if you iter through the children (which requires a hashtable > > > lookup). And it is a bit simpler to implement. > > > > That sounds good! > > > > > That said, we could have both approaches. > > > > I've mostly implemented (but not fully tested) the above approach. (See > > the attached screenshot.) I might have a go with your suggestion and see > > how they compare. > > Maybe you can commit something, so I can look at it? Sure. I'll start by committing the low level bits (C parts + low level Haskell wrapper) and post the high level bits to the list for review. > No rush, though, I'm supposed to work on my PhD (and spent the whole > morning adding the onInsertText signal). Heh. Yeah that signal bocking/stopping stuff is less than pleasant. Duncan |
From: Axel S. <A....@ke...> - 2005-12-07 14:59:53
|
On Wed, 2005-12-07 at 13:57 +0000, Duncan Coutts wrote: > > > where setBitSliceWord32 :: Word32 -> Int -> Int -> Word32 -> Word32 > > > setBitSliceWord32 word off count value = > > > let mask = (1 `shiftL` count - 1) `shiftL` off > > > in (word .&. complement mask) .|. (value `shiftL` off) > > > > Ok, so you can do it :-) > > Yeah, ok that was overkill. :-) > I could have just said "actually it's not too hard". Sorry that wasn't meant to be condescending. I guess my point was that it can be done, but it might be a bad trade-off since it is rather tricky to get right and it's not clear if it has any advantages over an array of arrays. I wonder what the API would look like if people where to implement their own Model. Is your indexing scheme already part of a model (i.e. we pass three integers from the TreeIter for people implementing their own model) or is this built into the library (i.e. people implementing a model get a TreePath). > > > > To advertise my suggestion... Advancing iterators is O(1) in all cases, > > > > even if you iter through the children (which requires a hashtable > > > > lookup). And it is a bit simpler to implement. > > > > > > That sounds good! > > > > > > > That said, we could have both approaches. > > > > > > I've mostly implemented (but not fully tested) the above approach. (See > > > the attached screenshot.) I might have a go with your suggestion and see > > > how they compare. > > > > Maybe you can commit something, so I can look at it? > > Sure. I'll start by committing the low level bits (C parts + low level > Haskell wrapper) and post the high level bits to the list for review. I'm not so worried that your high level stuff doesn't work. Committing is fine by me. > > No rush, though, I'm supposed to work on my PhD (and spent the whole > > morning adding the onInsertText signal). > > Heh. Yeah that signal bocking/stopping stuff is less than pleasant. I added two functions called stopTextInsert and stopDeleteInsert. That begs the question if we should have these functions for all handlers... Axel. |
From: Duncan C. <dun...@wo...> - 2005-12-07 16:39:15
|
On Wed, 2005-12-07 at 14:57 +0000, Axel Simon wrote: > On Wed, 2005-12-07 at 13:57 +0000, Duncan Coutts wrote: > > > > > where setBitSliceWord32 :: Word32 -> Int -> Int -> Word32 -> Word32 > > > > setBitSliceWord32 word off count value = > > > > let mask = (1 `shiftL` count - 1) `shiftL` off > > > > in (word .&. complement mask) .|. (value `shiftL` off) > > > > > > Ok, so you can do it :-) > > > > Yeah, ok that was overkill. :-) > > I could have just said "actually it's not too hard". > > Sorry that wasn't meant to be condescending. S'ok I didn't think you were :-). > I guess my point was that > it can be done, but it might be a bad trade-off since it is rather > tricky to get right and it's not clear if it has any advantages over an > array of arrays. I wonder what the API would look like if people where > to implement their own Model. The lowest level api looks like this: data CustomStoreImpl = CustomStoreImpl { customStoreGetFlags :: IO [TreeModelFlags], customStoreGetNColumns :: IO Int, customStoreGetColumnType :: Int -> IO GType, customStoreGetIter :: TreePath -> IO (Maybe Iter), -- convert a path to an iterator customStoreGetPath :: Iter -> IO TreePath, -- convert an iterator to a path customStoreGetValue :: Iter -> Int -> GValue -> IO (), -- get the value at an iter and column customStoreIterNext :: Iter -> IO (Maybe Iter), -- following row (if any) customStoreIterChildren :: Maybe Iter -> IO (Maybe Iter), -- first child row (if any) customStoreIterHasChild :: Iter -> IO Bool, -- row has any children at all customStoreIterNChildren :: Maybe Iter -> IO Int, -- number of children of a row customStoreIterNthChild :: Maybe Iter -> Int -> IO (Maybe Iter), -- nth child row of a given row customStoreIterParent :: Iter -> IO (Maybe Iter), -- parent row of a row customStoreRefNode :: Iter -> IO (), -- caching hint customStoreUnrefNode :: Iter -> IO () -- caching hint } data Iter = Iter !Word32 !Word32 !Word32 So there are ops to convert paths to Iters and vice versa and then most of the other ops are in terms of Iters. > Is your indexing scheme already part of a model (i.e. we pass three > integers from the TreeIter for people implementing their own model) or > is this built into the library (i.e. people implementing a model get a > TreePath). The Iter is just 3 words and the model implementation can use them however it likes. (Sadly Iters cannot be arbitrary Haskell types due to memory management restrictions) So the indexing system I've been using for the TreeStore implementation is separate and not built into the API. > > Sure. I'll start by committing the low level bits (C parts + low level > > Haskell wrapper) and post the high level bits to the list for review. > > I'm not so worried that your high level stuff doesn't work. Committing > is fine by me. I'm not sure where it should go yet. I'll post the high level stuff separately. > > Heh. Yeah that signal bocking/stopping stuff is less than pleasant. > > I added two functions called stopTextInsert and stopDeleteInsert. That > begs the question if we should have these functions for all handlers... So the ConnectId currently contains the object and the signal instance id. If it also contained the signal name/number then we could stop any signal via its ConnectId. So actually I think it's easy: type HandlerId = {# type gulong #} type SignalId = {# type guint #} data GObjectClass o => ConnectId o = ConnectId !HandlerId !SignalId !o connectGeneric signal after obj user = do sptr <- newStablePtr user gclosurePtr <- gtk2hs_closure_new sptr withCString signal $ \signalPtr -> withForeignPtr ((unGObject.toGObject) obj) $ \objPtr -> do handlerId <- {# call g_signal_connect_closure #} (castPtr objPtr) signalPtr (GClosure gclosurePtr) (fromBool after) signalId <- signalLookup objPtr signalPtr return $ ConnectId handlerId signalId obj which uses: signalLookup objPtr signalPtr = do gclass <- {# get GTypeInstance->g_class #} objPtr gtype <- {# get GTypeClass->g_type #} gclass {# call unsafe g_signal_lookup #} signalPtr gtype and then we can stop an emission using just the ConnectId. signalStopEmission (ConnectId handler signalId obj) = withForeignPtr ((unGObject.toGObject) obj) $ \objPtr -> {# call g_signal_stop_emission #} (castPtr objPtr) signalId 0 However I'm not really sure this is ideal either. It is possible to stop an emission with just the object and the signal name/id. But this api and the existing stopInsertText you've added requires a connected signal handler. If we were ever to change it, I think a better api for signals might be: clickedEvent = Event connect_NONE__NONE "clicked" and then: upon (Event connect, name) = connect name False after (Event connect, name) = connect name True So we'd say: upon clickedEvent myButton $ do ... rather than: onClicked myButton $ do ... It would also mean that we could do what wxHaskell does and allow signals to be set like properties: set myButton [ buttonLabel := "Blah", on clickedEvent := do ... ] but in particular it'd also allow us to say: signalStopEmission myButton clickedEvent without needing to have an already connected signal. Duncan |
From: Duncan C. <dun...@wo...> - 2005-12-08 19:13:33
|
On Wed, 2005-12-07 at 14:57 +0000, Axel Simon wrote: > > > Maybe you can commit something, so I can look at it? > > > > Sure. I'll start by committing the low level bits (C parts + low level > > Haskell wrapper) and post the high level bits to the list for review. > > I'm not so worried that your high level stuff doesn't work. Committing > is fine by me. Ok the low level bits are in cvs now. The C part: gtk/Graphics/UI/Gtk/TreeList/Gtk2HsStore.c The Haskell wrapper: gtk/Graphics/UI/Gtk/TreeList/CustomStore.chs The only api it exports is: customStoreNew :: CustomStore -> IO TreeModel where a CustomStore is the interface I described before in another email: data CustomStore = CustomStore { ... } data Iter = Iter !Word32 !Word32 !Word32 Now, attached are implementations of the above interface and a test program. Column.hs just has a definition of the Column data type (and some associated utility functions) which is used by both the implementations data Column a = ColumnString (a -> String) | ColumnInt (a -> Int) | ColumnBool (a -> Bool) of course this should be extended to cover all the types that can be used as columns types in a GtkTreeModel. Then we have ListStore which exports just makeListStore :: [Column a] -> [a] -> CustomStore I'm not sure this is the right API yet since the return type should probably be a different type (say ListStore) since it's going to support other operations (ie to modify the store after it's created). The implementation of makeListStore is quite simple. The TreeStore module is obviously rather more complex though the exported api is pretty similar: makeTreeStore :: [Column a] -> [Tree a] -> CustomStore Duncan |
From: Duncan C. <dun...@wo...> - 2005-12-08 15:56:28
|
On Wed, 2005-12-07 at 17:28 +0000, Axel Simon wrote: > > > I'm not so worried that your high level stuff doesn't work. Committing > > > is fine by me. > > > > I'm not sure where it should go yet. I'll post the high level stuff > > separately. > > We should probably replace the ListModel and TreeModel files with a > default implementation. Ok, but not yet, since what I've written so far does not have any functions for modifying the store. > Yes, true. I nearly did this, too, but I thought it's kind of naff to > lookup the signal id just for this one function that nobody uses. But I > guess I do this anyway: I can use g_signal_connect_closure_by_id and > then pass signalId instead of the name. That way the new closure stuff > is no slower. But of course I have to change the callback generator as > well. That's a good point. > You can only stop a signal when it is currently emitted, otherwise you > get a warning. Hence it seems quite awkward to stop a signal that you're > not currently handling. Thus, you should always have a ConnectId. Yeah, fair enough. Though it does need the IORef trick or recursive do to actually get at the ConnectId inside the handler. > > If we were ever to change it, I think a better api for signals might be: > Yes, that looks nice, although you loose > myBotton `onClicked` do > ... > > which I use all the time. I used to use that style, but I've totally changed to using: onClicked myButton $ do ... since that works for callback registration functions that take more than one parameter. > > It would also mean that we could do what wxHaskell does and allow > > signals to be set like properties: > I'm willing to change this. While we're thinking about future possible API changes I'll float some ideas I've been vaguely thinking about for some time: Like Yampa and wxHaskell, we should allow the setting of properties in the constructor: myButton <- Button.new [ label := "foo" ] I think this would allow us to make quite a bit of user code more concise. For example we could nest thing where the name of the intermediate object is not very important: myWindow <- Window.new [ containerChild :=> VBox.new [ containerChild := myButton1, containerChild := myButton2, ] ] Actually that is probably not a very good example since I'd also like to add a layout property to containers and some layout construction functions (which basically construct VBoxs & HBoxs) myWindow <- Window.new [ layout := layoutVertical [myButon1, myButton2] ] One thing that allowing one to set properties at construction time would help with is the profusion of construction functions which basically take various optional parameters: myView <- treeViewNew myView <- treeViewNewWithModel model vs. myView <- TreeView.new [] myView <- TreeView.new [ model := myModel ] It'd help on cutting down the size of the documentation. Another thing we could do on that front is to encourage the use of the attributes rather than the corresponding get/set functions. We could even remove the get/set versions from the documentation. A harder thing which does require a language extension is to be able to say what I've been writing above, that is Foo.new rather than fooNew. There has been some discussion of an extension to the hierarchical library system that would allow this. Another one which we have mentioned before is to use classes to allow us to use the same names for attributes on different objects that are really the smae thing, eg the label & text properties on many objects. It would probably also help with signals. Many objects have some default activate/clicked/pressed event. > BTW why is Gtk2Hs marked as "Production/Stable" and not as "Beta" on > Sourceforge? As long as we haven't release 1.0, we should change the > API for the better (and hence not claim that it is Stable). Yeah, quite right. I was thinking "Production" was true, but yes we're not guaranteeing API stability yet. Fixed. Duncan |