Archive

Posts Tagged ‘wxHaskell’

How does wxHaskell event handling work – part 1

June 17, 2011 1 comment

Some Background

As some of you may have noticed, there has been a minor flurry of activity (if that’s not an oxymoron) from me recently. The wxHaskell repo went offline after the attack on code.haskell.org and I decided to look at support for wxWidgets 2.9 as well as 2.8 while I was waiting for the repo to be restored.

Something I thought I had fixed (would have known better if I had been reading my mail more carefully) was a bug found by Eric when using the native Mac wxWidgets. Basically Eric was seeing an error dialog indicating an assertion failure, which it seemed to be safe to ignore, when starting an application.

The offending code, which you will find in eljevent.cpp, was:

EWXWEXPORT(wxClosure*,wxEvtHandler_GetClosure)(wxEvtHandler* evtHandler,
                                               int id,int type)
{
  wxCommandEvent  event(type,id);     //We can use any kind of event here
  wxCallback*     callback = NULL;
  bool            found    = false;

  getCallback = &callback;
  found = evtHandler->SearchDynamicEventTable( event );
  getCallback = NULL;

  if (found && callback)
    return callback->GetClosure();
  else
    return NULL;
}

I put in a workaround for the problem (green text below) in what looked like an obvious way (given that ignoring the problem seemed OK – and yes, I know that this is a horrible hack)

if (evtHandler->GetDynamicEventTable() != NULL)
 found = evtHandler->SearchDynamicEventTable( event );

Unfortunately, Eric had already discovered that ignoring the problem wasn’t really an answer

I’m afraid this causes the (surprise!) event handlers for some list boxes to stop working :-(

That’ll teach me not to take the time to understand a problem before ‘fixing’ it. OK, so Laziness and Impatience failed. Time for Hubris to take over and I tweeted that I’d look into the problem properly.

In fact, the code in wxEventHandler_GetClosure() is rather dubious in a couple of respects. The first is that the static global ‘getCallback’, is clearly a very long-standing hack (the repo source code notes this – I tend to remove comments to keep blog entries to some sort of sane length). The second is that wxWidgets doesn’t document wxEvtHandler::SearchDynamicEventTable() at all. Since wxWidgets is generally very well documented, this is a warning…

How does Event handling normally work in wxWidgets?

There are a couple of ways to do event handing in wxWidgets. The simplest, which I’m not going to explore further, is to use event macros. This won’t help us in wxHaskell as we want to define our event handlers in Haskell. The solution is to use wxEventHandler::Connect() to associate a function call with an event. This means that our supplied function will get called when the event occurs. A little plumbing will be needed to allow the called function be be written in Haskell, which we’ll look at later.

So what happens when an event occurs? The following is shamelessly cribbed from the wxWidgets documentation:

When an event is received from the windowing system, wxWidgets calls wxEvtHandler::ProcessEvent on the first event handler object belonging to the window generating the event.

The normal order of event table searching by wxEvtHandler::ProcessEvent() is as follows:

  1. If the object is disabled (via a call to wxEvtHandler::SetEvtHandlerEnabled) the function skips to step (6).
  2. If the object is a wxWindow, ProcessEvent is recursively called on the window’s wxValidator. If this returns true, the function exits.
  3. SearchEventTable is called for this event handler. If this fails, the base class table is tried, and so on until no more tables exist or an appropriate function was found, in which case the function exits.
  4. The search is applied down the entire chain of event handlers (usually the chain has a length of one). If this succeeds, the function exits.
  5. If the object is a wxWindow and the event is set to set to propagate (in the library only wxCommandEvent based events are set to propagate), ProcessEvent is recursively applied to the parent window’s event handler. If this returns true, the function exits.
  6. Finally, ProcessEvent is called on the wxApp object.

Connecting event handlers in wxHaskell

Now that the outline of event handling in wxWidgets is clear, let’s look at events in wxHaskell. I’m going to do this starting from the WXCore library as the WX library, while simpler to use, is just a Haskell wrapper around WXCore.

Widget wrappers need to provide at least two event-handler related functions: one to set the event handler and another to retrieve it. The setters and getters resemble the following (this example for Button):

buttonOnCommand :: Button a -> IO () -> IO ()
buttonOnCommand button eventHandler
  = windowOnEvent button [wxEVT_COMMAND_BUTTON_CLICKED] eventHandler (\evt -> eventHandler)

buttonGetOnCommand :: Window a -> IO (IO ())
buttonGetOnCommand button
  = unsafeWindowGetHandlerState button wxEVT_COMMAND_BUTTON_CLICKED skipCurrentEvent

The setter calls a generic event handler setter for everything deriving from Window with the control to which the handler applies, a list of the events for which the handler applies and an event handler function which will be called when the event occurs. One small point to note is that the event handler is passed twice: once as a piece of state and a second time wrapped in a lambda function.

The getter function uses an unsafe function to obtain the event handler for the requested event.

Digging further into the setters

Looking further into windowOnEvent, what do we have?

windowOnEvent :: Window a -> [EventId] -> handler -> (Event () -> IO ()) -> IO ()
windowOnEvent window eventTds state eventHandler
  = windowOnEventEx window eventIds (\ownerDelete -> return ()) -> eventHandler

Similarly, windowOnEventEx is defined as:

windowOnEventEx :: Window a -> [EventId] -> handler -> (Bool -> IO ()) -> (Event () -> IO ()) -> IO ()
windowOnEventEx window eventIds state destroy eventHandler
  = do
    let id = idAny
    evtHandlerOnEvent window id id eventIds state destroy eventHandler

In other words, we eventually transform the event handler for any given control into a generic event handler for a Window a (remember, using phantom types, wxHaskell arranges that all controls eventually resolve to a Window a), and this contains a function (strictly an IO computation) which is run when the event is triggered, and a computation which is run when the event handler is deleted. This computation takes a True argument if the owner is deleted and a False argument if we are simply disconnecting the event handler.

Digging further, we have:

evtHandlerOnEvent :: EvtHandler a -> Id -> Id -> [EventId]
                  -> handler -> OnEvent
evtHandlerOnEvent object firstId lastId eventIds state destroy eventHandler =
  do evtHandlerOnEventDisconnect object firstId lastId eventIds
     evtHandlerOnEventConnect object firstId lastId eventIds state
                              destroy eventHandler

The call to evtHandlerOnEventDisconnect simply ensures that any existing event handler is cleaned up before we install a new one. In this investigation we are more interested in:

evtHandlerOnEventConnect :: EvtHandler a -> Id -> Id -> [EventId]
                          -> state -> OnEvent
evtHandlerOnEventConnect object firstId lastId eventIds state
                         destroy eventHandler =
  do closure <- createClosure state destroy eventHandler
     withObjectPtr closure $ \pclosure ->
     mapM_ (connectEventId pclosure) eventIds
  where
    connectEventId pclosure eventId =
      evtHandlerConnect object firstId lastId eventId pclosure

We are finally getting close to where we connect up with the wxWidgets framework. The Closure a type is derived (using phantom types) from wxObject a which is itself an instance of Object a, which is basically a way of storing a C pointer.

data Object a = Object !(Ptr a)
              | Managed !(ForeignPtr (TManagedPtr a))

Now, for our purposes, we are generally using the simple case of a Ptr a Object, so looking back up at evtHandlerOnEventConnect, what we first do is to create a Closure instance:

createClosure :: state -> (Bool -> IO ()) -> (Event () -> IO ())
              -> IO (Closure ())
createClosure st destroy handler =
  do funptr  <- wrapEventHandler eventHandlerWrapper
     stptr   <- newStablePtr (Wrap st)
     closureCreate funptr (castStablePtrToPtr stptr)
   where
     eventHandlerWrapper :: Ptr fun -> Ptr () -> Ptr (TEvent ()) -> IO ()
     eventHandlerWrapper funptr stptr eventptr =
     do let event = objectFromPtr eventptr
        prev <- swapMVar currentEvent event
        if (objectIsNull event)
        then
          do isDisconnecting <- varGet disconnecting
             destroy (not isDisconnecting)
             when (stptr/=ptrNull)
               (freeStablePtr (castPtrToStablePtr stptr))
             when (funptr/=ptrNull)
               (freeHaskellFunPtr (castPtrToFunPtr funptr))
        else handler event
        swapMVar currentEvent prev
        return ()

In createClosure we wrap some state information, an event handler action and an action to perform when the event handler is disconnected. We obtain stable pointers to these (if they are not already stable pointers) and construct a closure which contains them. This construction can be safely passed to the C++ world without risk of being garbage collected in the Haskell world.

If we now go back to evtHandlerOnEventConnect, it should hopefully be clear that we are calling evtHandlerConnect with a window, a set of event IDs to handle and a closure containing the Haskell components which will be invoked when an event arrives.

Having looked at how event handlers get connected in wxHaskell, next time we’ll look at how they get called in some more detail.

Haskell Platform 2010.1.0.0 and wxHaskell on Windows

Edit: Please note that the issue described in this post has been fixed on newer versions of Haskell Platform, so you shouldn’t need to follow the instructions here.

Dan Haraj asked a question about installing wxHaskell on a Windows 7 machine. Since he was having problems, and since I have never verified wxHaskell installation on Windows 7, I agree to look into the problem.

Bottom line is that wxHaskell is fine on Windows 7, but there’s a problem with the Haskell Platform 2010.1.0.0 Windows installation. The issue is that the GHC version used doesn’t include C++ support, and wxHaskell needs this to build.

You have a couple of options for installing wxHaskell on Windows:

  1. Just use an older Haskell Platform installer – these work fine.
  2. Copy the required C++ support into Haskell Platform 2010.1.0.0 yourself from a MinGW installation. This is a horrible hack, as the files you will be copying to not correspond exactly to the MinGW version shipped in the Haskell Platform. They are close enough to work, however, if doing this doesn’t mortally wound your sense of software aesthetics.

You will need a copy of MinGW with gcc 3.4.5 installed, including C++ support. The automated installer at Sourceforge will install the same versions I used – just remember to tick the box marked ‘C++’ in the installer.

Once you have a suitable MinGW installation, do the following:

  • Copy cc1plus.exe from a MinGW 3.4.5 install into c:\Program Files (x86)\Haskell Platform\2010.1.0.0\mingw\libexec\gcc\mingw32\3.4.5
  • Copy libstdc++.a from a MinGW 3.4.5 install into c:\Program Files (x86)\Haskell Platform\2010.1.0.0\mingw\lib
  • Copy include\c++ directory from a MinGW 3.4.5 install into c:\Program Files (x86)\Haskell Platform\2010.1.0.0\mingw\include\c++

If you are using Windows 7, you will need to have Administrator privilege to do this.

After this, you can install wxHaskell on Windows 7 as follows:

  1. Open a cmd.exe window running as Administrator (press Start, type cmd into the search box and right click on the cmd icon to use the ‘Run as Administrator’ option.
  2. Type cabal install wx

You should have a working wxHaskell installation in a few minutes.

A couple of notes:

  • cabal install wx –user doesn’t work on Windows 7 without Administrator privilege, which kinda defeats the object. I will need to look into the reason for this, but it’s not at the top of my list.
  • You can’t run cabal install wx in an MSys shell. It breaks horribly. This means you build wxWidgets in a different shell to the one in which you install wxHaskell.

I’ve updated the wiki to reflect this…

Categories: wxHaskell Tags: ,
Follow

Get every new post delivered to your Inbox.