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:

  1. the composite should behave like an ordinary widget to the eyes of the application programmer
  2. be safe to use
  3. be easy to use
  4. 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:

  1. the composite should behave like an ordinary widget to the eyes of the application programmer
  2. be safe to use
  3. be easy to use
  4. 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.

6 Responses to “Proposal: Adding composability to WxHaskell”

  1. andrew frank Says:

    I am interested and think it is a good idea – at least much simpler code than any other i have seen (both for typing and for composition).

    thanks!

    andrew

    ps. the intEntryExample.hs is not attached, but twice the listExample – can you fix this?

  2. andrew frank Says:

    it took me a moment to understand that the missing lines in the exampe are:

    import Common.Composite

    type IntEntry = Composite () (IO Int, Int -> IO ())

    the type is obvious – after one has figured it out!

    could you show in the example how one could click on the intEntry and then have some action performed, e.g. putStr?

    thank you!

  3. supermule Says:

    Hi Andrew,

    Yes, adding the IntEntry -type in the example would make it a lot clearer. And I made a link, in the IntEntry section, to the full IntEntryExample.hs source code.

    The IntEntryExample.hs is acturally linked in the introduction, but the comment in IntEntryExample.hs is erroneously from ListExample.hs.

    Extending the IntEntryExample as you describe sound interesting. I will look into it.

    Greetings,

    Mads Lindstrøm

  4. andrew frank Says:

    Finer Control on Widgets

    hi Mads
    i got it working and tried some tests. great!
    (it would be helpful to say somewhere, that for user defined types – even for the simple tuple example – the $(derive ..)
    and the instances for WxGen are neecessary.

    what i would need is a finer control on what and how the individual widgets appear. assume a type like
    newtype XType = XType {unx: Int}
    which gives a bordered box with title XType and a label unx before the entry field. too much. i assume there is somewhere a way to control and reduce this. i would like to have just a plan typed entry box to which i can add label, border etc. as it fits.

    any idea?

  5. andrew frank Says:

    on enterKey := …

    dear mads

    it seems not possible to get the ‘enterKey’ event – but this is standard for most applications: when you hit enter, the process is started (as if you were hitting the button).

    i looked at your code for integer entry and saw that you capture all key events (on anyKey) but i seem not even there to get the return (c == ‘\r’). is this fixable such that code like
    set en [on enterKey := get en widgetValue >>= print ]
    could be inserted in the tuple example?

    interesting set of routines! now i will have to read typeable!
    thank you

    andrew

  6. supermule Says:

    Hi Andrew,

    I am afraid you stumped into a bug in the composability proposal :( When I initially made the proposal I assumed that all events would be automatically propagated to parent widgets. This is not the case http://docs.wxwidgets.org/2.8.6/wx_eventhandlingoverview.html#eventprocessing . Actually, only command events are propagated to parent widgets.

    One workaround is to add the event propagation ourselves, as in (see line starting with “on mouse”) http://haskell.pastebin.com/f2eeadecd

    Now events are propagated and this code will work as expected, see http://haskell.pastebin.com/f7b9f24d6

    Clearly, a more systematic way of handling event propagation would be better. One could extend the compose-function to attach event propagators to all widgets. However, do we always want events to be propagated? What do you think Andrew?

    Another option would be to let the widget-programmer decide which events are propagated.

    /Mads Lindstrøm


Leave a reply to supermule Cancel reply