From: Ben F. <ben...@on...> - 2009-03-21 01:10:11
|
Hello Experts this is probably a FAQ: what is the proper way to create new widget types? For concreteness, let's say, you want a (horizontal) composition of two widgets. It should act and behave just like a built-in widget. The obvious idea is to 'derive' from HBox: newtype MyWidget = MyWidget HBox In order to make it apparent that this is a widget and that it is derived from HBox, I do -- no instance HBoxClass MyWidget, interface is too general -- no instance BoxClass MyWidget, interface is too general instance ContainerClass MyWidget instance WidgetClass MyWidget instance ObjectClass MyWidget instance GObjectClass MyWidget where toGObject (MyWidget o) = toGObject o unsafeCastGObject = MyWidget . unsafeCastGObject So far, so good. BUT: how can I give MyWidget additional state? Say I have implemented a feature where one of the two sub-widgets can be 'selected', and I want client code to find out which. The obvious thing to do is data MyWidget MyWidget { box :: HBox, selection :: IORef (Maybe Widget) } But then, how do I write the GObjectClass methods? I think casting would be a very bad idea! OTOH, if I stick to the newtype, how can I add attributes? There is one solution I found after searching for a while, but it has a certain ugliness... In System.Glib.GObject there is objectCreateAttribute :: GObjectClass o => IO (Attr o (Maybe a)) The problem is that this is an IO action. But you want to call this only once, right? Remember that I want MyWidget to be indistinguishable from built-in ones. So what I do is this: newtype MyWidget = MyWidgetTableView HBox data MyWidgetState = MyWidgetState { selection :: IORef (Maybe Widget), ... } -- not exported, at least: {-# NOINLINE myWidgetState #-} myWidgetState :: Attr MyWidget (Maybe MyWidgetState) myWidgetState = unsafePerformIO objectCreateAttribute -- exported: myWidgetSelection :: ReadAttr MyWidget (Maybe Widget) myWidgetSelection = readAttr getter where getter mw = do maybeState <- mw `get` myWidgetState case maybeState of Nothing -> error "GAAA! Wrong object type!" Just state -> readIORef (selection state) myWidgetNew = do hbox <- ... let myState = ... ... let mw = MyWidget hbox mw `set` [myWidgetState := Just myState] return mw This works, but note the evil global variable (myWidgetState) which I'd rather like to get rid of. Is there any way? Do we need (or rather, want) a Gtk monad? (I think not.) Cheers Ben |