follow me on Twitter

    Blog has moved to, see September, 2010 on new site

    Blog entries for September, 2010

    This post moved to

    I'm not the sort of programmer that runs around quoting design patterns and drawing UML diagrams.

    However, maybe it's useful to discuss this rule of thumb, in the context of toolkits such as GTK+ and Clutter: It's best to avoid code that "knows about" the scene graph or toolkit as a whole.

    Here are some approaches I like, along with examples that came to mind. I hope nobody takes anything personally.

    Containers forward recursively to children.

    At the root of the tree, the root container (such as GtkWindow or ClutterStage) gets context from the platform. It then passes information or operations down to children of the root, the children pass on beyond that, etc.

    Example: the new GtkWidget draw() method passes down a cairo context which is transformed as it goes down recursively.

    Example: HippoCanvas does both drawing and events in this way. Events are translated as they go down through the tree.

    Bad: gtk_propagate_event() just walks the widget tree directly from outside the widget tree. This logic makes it annoying to nicely integrate a widget in a new context, such as in a Clutter scene graph.

    Children bubble up information to containers.

    Children can report news up to their parent container, which can in turn hand it up the chain.

    Example: clutter_actor_queue_redraw() works by having each child notify its parent that it needs to repaint.

    Bad: gtk_widget_queue_draw_area() walks up the widget tree to find a parent with a GdkWindow it can poke at. Instead, the container that has the window should contain the logic to invalidate the window, and nobody else in the tree should know anything about GdkWindow. (Note how the existence of GdkWindow has been leaked out so all widgets have to know about it. In the alternative design, only widgets that have windows need to know about them.)

    Interfaces provided by containers.

    A special case of bubbling up to containers is to ask containers for an interface to use - delegate to the parent, in effect.

    Rather than using global singletons, child actors or widgets can ask their container for what they need. Containers then have the opportunity to override anything that the child sees by reimplementing the interface, possibly delegating most methods back up to the implementation they received from their own parent.

    Example: HippoCanvasContext is a grab-bag interface provided by containers to children, providing a very reduced and simplified set of things a widget might need, such as a Cairo surface or Pango layout specialized for the window system - or print-to-PDF operation - currently in progress. The context is used to obtain a PangoLayout or a Cairo context, which are also abstractions. The parent container can pick the right settings and backend for Pango and Cairo.

    Bad: gtk_grab_add() goes straight to the toplevel window and then to a global list of window groups. Instead, widgets could ask their container for a grab, each container could do grabbing within that container, and it would recursively move up to the toplevel; the toplevel would then deal with the window groups. By delegating this, it could even be possible to make grabs work on a tree that contains non-GtkWidget in it.

    Use interfaces rather than concrete objects.

    I don't see a lot of value to making a specific control, such as a GtkTextView, into an interface. However, the main "touch points" that form the core of a toolkit really ought to be. This includes GtkWidget, ClutterActor, and all the global state getters and setters (which in turn should come from the parent container, rather than using singletons).

    Interfaces let you make bridges between "worlds." If you're putting a widget or actor into a nonstandard context, whether it's a PDF printout, or a container that rotates or clips or clones, or drawing an actor inside a widget or a widget inside an actor, the cleanest solution will involve reimplementing interfaces to match the context.

    Use model-view rather than omniscient objects.

    This one seems obvious, but isn't always done.

    Bad: gtk_main_do_event() hardcodes knowledge of other bits of GTK+, for example invoking _gtk_settings_handle_event() and _gtk_clipboard_handle_event(). These should be connecting to a signal so they don't leak out of their own modules. gtk_propagate_event() is another nasty piece of non-modularity. Future direction: add event signals to GdkWindow and GdkDisplay and drop this central dispatch mess.

    Bad: Clutter master clock should be a model, not a controller. It knows about stages and timelines specifically and just tells them what to do. Instead of saying "anything that does repainting, repaint now" it says "repaint the stage." If this code were model-view, it could simply be dropped into GTK+ and used there as well, for example. Flexibility.


    Replace code that knows about "the world" with:

    • Containers that know about only their immediate children.
    • Children that know only about their parent and purpose-built interfaces provided by the parent.
    • Models with multiple views.

    Widgets and actors should know about their parents and their children, never their grandparents, never their grandchildren, and certainly not strangers they met in a singleton bar.

    Getting this right in Clutter and GTK+ would make both toolkits more robust in situations where the two toolkits are mixed - something I'd like to see much more of - and in situations where the toolkits are used in odd ways, such as in a window/compositing manager, or just in any creative way that isn't quite what was intended. In short, getting this right makes the code more modular and reusable and clear, not to mention less fragile.

    Disentangling widgets and actors from the "global view of the world" is great for unit testing, as well.