three years ago Neil and I wrote the Model
API, to be included in Clutter. We tried encode in the design what we learned from the experience with GtkTreeModel
, and while it could be said that there are shortcomings (a base class instead of an interface, no bulk operations, some corner cases in the iterators API) I think we managed well enough not to repeat the same issues (boxed types, volatile iterators, and conflating a Tree and a List API into one) on top of those.
still, we made what I now think is the same design mistake all over again: we tried to provide a way to write MVC applications with Clutter, and we ended up collapsing the model with the controller — that is: we added a new data storage class that notifies you when something changed inside it ((gtk+, in a way, allows you to bolt a controller on top< of a data storage, but you still need the data storage to be a class inside the type system otherwise you won’t be able to implement the GtkTreeModel
interface)).
I now think it’s a mistake trying to conflate the data storage with the actual object notifying the views about changes: the controller should just be notified by the model and notify the views
GLib already provides a lot of data storage types: GArray
, GPtrArray
, GHashTable
, etc. — it would seem sensible to just use them and just wrap the insertion and removal functions instead of:
- create a
GObject
wrapper around a data structure; - wrap insertion, removal and iteration operations;
- add specialized code and signals to handle the changes and notify the views;
on top of these, if you want to write a generic storage you’ll have to:
- make every entry point a virtual function, to allow sub-classes overriding or providing a default behaviour;
- provide a generic iterator API;
- wrap everything with
GValue
s;
in essence, the complexity of the storage quickly balloons out of control — and all because you wanted to notify another object that you added a new item inside a GPtrArray
((and no: I don’t think that keeping the complexity under check by losing generality is a good trade-off; it’s just going to bite you in the rear later on)).
wouldn’t it be good to have the “notify somebody that $SOMETHING
changed inside a data storage” thing nailed down with a generic API?
I did think it would be good, so I spent some free cycles last week to implement a generic Controller — the thing that notifies a view that something changed. it requires minimal additions to already existing data storage types provided by GLib — to the point that you don’t really need a GObject
wrapper around your model altogether.
the overall design is explained on the GNOME wiki, so I won’t rewrite it down here; and yes: if, by a cursory glance, it looks a lot like a certain platform’s API it’s because I think it’s a good representation of a correct approach.
the code lives in the GNOME Git repository; it currently has a stub LGPLv2.1 license because I think it should be seen as a 1700 lines patch to GObject/GIO under version control, and not as a stand-alone project ((it also gave me the chance to play with non-recursive autotools layouts, but that was just a side-effect)).
there are some things left to do, notably a GObjectController
which I think I described to a colleague as GObject::notify on PCP; for that to happen, though, I’ll need some changes in GObject
itself.
Very nice!
Why does GControllerReference need to be a GObject? Seems quite an overkill to me.
@Mardy: because Controller sub-classes might decide to add more meta-information than what’s currently stored inside it without requiring changes to the Reference itself. also, boxed types need to be either fully opaque (and thus cannot be extended by third party code, ever) or completely set in stone (and thus cannot be changed by anyone, ever). I personally like boxed types, but only when they make a difference. if we created thousands of references, one for each operation, then I’d probably have used a boxed type to keep the memory usage at a minimum; but since references support bulk operations you need to create a single instance, and creating a single instance is not ever going to be problematic.
finally, introspection and bindings behave better with object than with boxed types.
Hi Emmanuele,
it took a couple of re-reads to see what you actually mean here, as some misuse of terms proved to be quite an effective smoke screen :)
First you assert that it is a bad idea to “collapsing the model with the controller — that is: we added a new data storage class that notifies you when something changed inside it”, yet provide an example on the wiki of something that is called a model, and both has state and provides change notifications. Granted, it *delegates* to a kind of change notifier helper called “controller” and a not-observable state holder, but c’mon, what’s the deal here?
In an ideal world there are (at least) two ways to go about doing it / calling it: decorating or adapting. With the former, you implement / extend the same interface as the object you delegate to, overriding some of its behavior, by implementing change notifications in this case. (clearly you need to extend the delegate with methods to add listeners). With the latter you do not claim to keep the semantics of your delegate. Both have its merits which I don’t intend to go in here.
Since, as you also point out as an issue, the current G* state holders (GArray et al) are neither observable nor interfaces, therefore only adapters can come in to play. I humbly suggest you ought to call them something like that. (BTW what you call GControllerReference is usually called an event object.)
As far as MVC goes, all this is *still* your Model layer however, you are not done with Controller, and that’s why calling GController a controller is confusing. In many data binding frameworks out there there is a glue layer between model and UI. Possible responsibilities in the binding layer include:
– adapting model API to binding API
– converting model value to UI value, and back (e.g. Date to String)
– adapting binding API to UI API
– validating values coming from the UI
I got the feeling you are also trying to fill in some of the void in this layer with the GController API, but these are arguably separate concerns, and *build upon* change notifications.
Greg
@Greg: yes, there is a murkiness in the terms — also not improved by the fact that everyone and his sister seems to be drawing the lines around the terms at their convenience, even when trying to give authoritative answers. :-)
the example on the wiki uses the term “model” because it’s an example meant for users of the current GNOME/GTK meaning of the term, for ease of understanding.
as for the naming: yes, it’s really an adapter, since it’s missing the accessors to the state storage that would complete the controller interface contract, in the strictest sense.
it could be argued that any sub-class of GController should also provide the accessors, the validation methods and the conversion methods from UI to state, which would make those controllers.