Proposal: Adding composability to WxHaskell March 16, 2008

Introduction

Currently there is no type safe way to make composite widgets with wxHaskell. We can compose two widgets and present them in a GUI. However, we cannot (type safely) compose two widgets into a larger widget, that behaves similarly to ordinary wxHaskell widgets (the Window w -kind). The only solutions that I have found is the CustomControls example in wxHaskell repository and as explained in this mailinglist post. Neither of them is type safe. These examples use unsafe casting of attributes and unsafe casting of the composite widget. Also as seen in the latter example, when widgets needs state they must be kept in a global hashtable, which results in more safety issues. The hashtable is created using unsafePerformIO.

Composability is important for most (maybe any) programming abstraction, and thus we would also like it for wxHaskell. Composability is also common in other GUI toolkit like AWT for Java.

In this post I will propose how we could add composability to wxHaskell.

The source code for this proposal can be found here Composite.hs, here ListExample.hs, and here IntEntryExample.hs.

In this post application programmer will refer to a person implementing a GUI application. Library programmer will refer to a person implementing the wxHaskell library.

Goals

But before going into details, I will state which goals I think is important for composing widgets:

the composite should behave like an ordinary widget to the eyes of the application programmer be safe to use be easy to use simple implementation

The first goal means that the composite type should implement many (if not all) of those type classes that wxHaskell widgets normally implements.

As stated above, current ways of making composite widgets involves unsafe type casting. This is of cause less than ideal. We do not want this or similar risky code.

And lastly, we would of cause want composing to be as easy as possible. Both for the application programmer and the wxHaskell library programmer.

How to compose widgets

In this section I will describe how the application programmer creates new composite widgets, rather than how the library programmer implements the composer functions, which will be shown in the next section. We will show how to compose widgets using two examples.

We create new widgets using the compose function:

compose :: (Panel () -> IO (Layout, super, user)) -> Window w -> [Prop (Composite super user)] -> IO (Composite super user)

which takes an IO action as input. The action should return a Layout, a super-type, and a user-type. The role of the super-type is to easily inherit some instances. E.g. if the super-type is SingleListBox () then we will inherit instances for Items, Selection, and Selecting. The role of the user-type is to let the composite-widget programmer instantiate arbitrary classes. We will see the use of super-type in the first example, and the user of user-type in the second example.

List-box example

We will create a composite widget containing a list and a button to delete elements in the list. The button should only be enabled when some element is selected in the list.

First, the code for the list-box widget:

type MyList = Composite (SingleListBox ()) () -- List with delete button myList :: Window w -> [Prop MyList] -> IO MyList myList = compose $ \p -> do ls <- singleListBox p [ ] b <- button p [ text := "Delete item" , on command := do s <- get ls selection when (s /= (-1)) (itemDelete ls s) , enabled := False ] set ls [ on mouse := \_ -> do s <- get ls selection set b [ enabled := (s /= (-1)) ] putStrLn "Mouse event" propagateEvent ] return (row 10 [ widget ls, widget b ], ls, ())

as can be seen this code resembles ordinary wxHaskell code, except for the compose function. Thus, it should be easy for the wxHaskell application programmer to get started. Next the code to use our new widget:

main :: IO () main = start $ do w <- frame [] -- here we use the new MyList widget ls <- myList w [ text := "My list", items := map show [1..7], fontSize := 18 , on select := print "Some item selected..." ] enableB <- button w [ text := "Outer enable" , on command := do set ls [ enabled := True ] ] disableB <- button w [ text := "Outer disable" , on command := do set ls [ enabled := False ] ] set w [ layout := row 10 [ widget ls, widget enableB, widget disableB ] ]

the main function creates a MyList and two buttons which can enable and disable the widget. Again this resembles ordinary wxHaskell code. The reader should note, that we do the right thing with respect to enabledness. If we hit the enable-button MyList’s delete button is only enabled if some item is selected. That is, the widget do not just blindly enable all of it’s child widgets.

Integer entry

This example will create a text entry specialised for integer values. This time we will not inherit the instances of a super-type but implement our own.

-- Text entry for integers type IntEntry = Composite () (IO Int, Int -> IO ()) intEntry :: Window w -> [Prop IntEntry] -> IO IntEntry intEntry = compose $ \p -> do intEn <- textEntry p [ processEnter := True , on anyKey := handleInput , text := "0" ] let getter = do val <- get intEn text readIO val setter x = set intEn [ text := show x ] return (widget intEn, (), (getter, setter)) where handleInput (KeyChar c) = do if c `elem` ['0'..'9'] then propagateEvent else return () handleInput _ = propagateEvent class IntValue a where intValue :: Attr a Int instance IntValue IntEntry where intValue = newAttr "Int attribute" getter setter where getter composite = fst (pickUser composite) setter composite = snd (pickUser composite)

as can be seen we made a new class for integer valued widgets. Our new composite widget implements this type.

Here is the full code for IntEntry.

Inherited instances

Regardless of using a super-type or not these instances:

Widget

Able

Bordered

Child

Dimensions

Identity

Literate

Visible

Reactive (event class)

are always inherited. This also means that we cannot specialise these instances for a particular widget. But behavior for these classes should be similar for all widgets. Thus, there is no need to specialise them.

If the super-type implements any of:

Items

Selection

Selections

Textual

Commanding (event class)

Selecting (event class)

then so will the composite widget.

Those it is possible to automatically inherit most of the ordinary wxHaskell instances.

Goals

The two examples show that we fulfil goal one though three, as the code resembles ordinary wxHaskell code, is fairly easy to use, and do not involve unsafe code.

Implementation of compose

We use a Composite type to contain the widgets. It is defined as follows:

data Composite super user = Composite { pickPanel :: Panel () , pickSuper :: super , pickUser :: user }

All of wxHaskell’s widgets are of type ‘forall w. Window w’, but the Composite type is not. However, this is not problematic, as the Composite type will still look like an ordinary wxHaskell widget to the application programmer, due to implementing most of the ordinary wxHaskell type classes.

The most interesting piece is properly the compose function:

compose :: (Panel () -> IO (Layout, super, user)) -> Window w -> [Prop (Composite super user)] -> IO (Composite super user) compose f w props = do p <- panel w [] (lay, super, user) <- f p set p [ layout := container p lay ] let composite = Composite p super user set composite props return composite

it simply creates a panel, sets the layout of the panel, and sets all properties. One should note that no properties are set at widget creation time. This may be problematic for functions like fullRepaintOnResize that requires to be set at creation time.

And now we can instantiate the ordinary Haskell classes:

-- Inherit from Panel () - all composites will inherit these classes instance Widget (Composite super user) where widget w = widget (pickPanel w) instance Able (Composite super user) where enabled = mapFromPanel enabled ... -- Inherit from super instance Checkable super => Checkable (Composite super user) where checkable = mapFromSuper checkable checked = mapFromSuper checked ...

We have not shown all the instantiated classes. However, these can be found at the accompanying source code.

The complete implementation (with comments) is 208 lines, where most of it is boilerplate class instantiations. While I would like it shorter, it is not overly long and the structure is fairly simple.

Conclusion

As shown the proposal lives up to the goals stated in the beginning:

the composite should behave like an ordinary widget to the eyes of the application programmer be safe to use be easy to use simple implementation

One problem with this proposal is that a small number of wxHaskell functions must be called at widget creation time. We might be able to alleviate this with type-safe casts. But this will have to wait for a future posts.

Another issue could be that Composite is not of type ‘forall w. Window w’. However, I do not see any real problem with this, but would very much like comments from people who do.

Feel free to discuss this proposal. Any improvements, critique, questions, or other comments will be most welcome.