wxHaskell News

A great deal has happened in wxHaskell land over the past few weeks, so I thought a summary was worthwhile

wxHaskell 0.90 Released

A significant update to wxHaskell was released on April 14th. This brings in all of the work done by Dave Tapley, Eric and many others to provide support for wxWidgets 2.9.

Supporting wxWidgets 2.9 is important for quite a number of reasons – not least because at some point it will become wxWidgets 3.0 and will be the new stable version. However in the short term the main benefit is support for 64bit OS platforms – notably MacOS X Snow Leopard and Lion.

The slightly odd version numbering convention was chosen to allow wxHaskell 0.13 to evolve without being excessively constrained over version numbering. In any case, it would be nice to get to version 1.0 at some time soon – perhaps when wxWidgets 3.0 is released.

Most of the future wxHaskell development effort will go on the new branch.

wxHaskell 0.13 Branch Created

On many systems, particularly almost all Linux distributions, wxWidgets 2.8.x remains the standard ‘package’ for wxWidgets, so we continue to support this for those who would prefer to use the packages provided by their distro. It also allows Windows users without C++ development environments to use the wxPack binary installers for wxWidgets.

Experimental GitHub Repository

I have created an experimental GitHub repository. It is right up to date at the time of writing and contains two active branches: master is the wxWidgets 2.9 repo and WXWIDGETS_2_8 is (unsurprisingly) the wxWidgets 2.8 branch.

I’m especially interested in feedback on whether moving definitively to GitHub would be a good thing – the main criteria for judgement being whether it makes it easier to receive contributions from others.

wxHaskell and wxWidgets 2.9

Those who follow the wxHaskell developer or users lists will know that over the last few months there has been a flurry of activity on wxHaskell, spurred on by Dave Tapley. I wanted to summarise what has been happening, and what stands in the way of a release supporting wxWidgets 2.9

<blatent-plug>Dave is working on wxHaskell with the agreement of his employer, Mentics Inc. I encourage any readers who have the opportunity to place business in Mentics’ direction to do so in appreciation of this generous donation of time and talent to the Haskell community.</blatent-plug>

Why wxWidgets 2.9?

Most Linux distributions currently supply wxWidgets 2.8.x libraries from their packaging systems, and Windows has the pre-built and readily installable wxPack containing wxWidgets 2.8.12, so why are we moving to an unstable release of wxWidgets?

There are many reasons, but two stand out in particular:

  • It is the future. wxWidgets 3.0 will be released from the 2.9 line, and provides major improvements. These include:
    • Much improved Unicode support.
    • Lots of new controls: ribbon bars, wxPropertyGrid, wxWebView, wxTreeListCtrl, wxRichToolTip, wxRichMessageDialog. Together, these simplify the creation of GUIs with a ‘modern’ look and feel.
    • The stc library (used by StyledTextControl) is now part of the main library build (as is SVG, which I would like to wrap for wxHaskell in the near future).
    • Support for 64bit OS X builds, which we have been unable to support on wxHaskell due to underlying lack of support in wxWidgets.
  • We need to clean up some legacy ‘cruft’ in the code. Daan originally wrote wxHaskell for wxWidgets 2.4.x, and things have moved forward a long way since then. This is an opportunity to remove deprecated functions and offer cleaner APIs.

What is changing?

The changes listed exist today in Dave’s development repository at DarcsDen, and will be mainlined in our master repository at code.haskell.org starting the coming weekend.

  • Newly wrapped classes
    • PropertyGrid, related helper types and sample code (Dave Tapley)
    • ListView (Dave Tapley)
  • Reinstated features
    • StyledTextCtrl (Dave Tapley)
    • OpenGL (Me)
    • Ability to use wxHaskell in GHCi (Dave Tapley)
  • Build system improvements
    • Some old Eiffel legacy code in the build system has been removed. Everything is now either C++ or Haskell (Eric Kow).
    • The C wrapper for wxWidgets has been moved into a separate project, wxC and built as a shared library (Dave Tapley).
    • Work to support OS X Lion (Eric and Alessandro Vermulen).
    • A Haskell native implementation of wx-config for use on Windows platforms. We are not sure if we will use this as yet – it is experimental (Eric).
  • Bugfixes and ‘build experience’ contributions from all of the above and several others , including Shelarcy, Maciek Makowski, Henning Thielemann, Peter Simons.

What is left to do before there is a release to Hackage?

Quite a bit!

The code in Dave’s repo is reasonably well tested on Linux (Ubuntu), but has currently received insufficient love on Windows or Mac. We will need it to work reliably on all three platforms before it can be released.

I think that the main blocker right now is probably determining the correct configuration for a Windows build. We have a couple of options here. We could develop Eric’s Haskell wx-config replacement so that it has been tested for a good subset of all possible wxWidgets build configurations, or we could fix wx-config-win to the same end. This has raised some philosophical questions on ‘Open Source and community’ which I am thinking of blogging on separately.

Most of all, when we have candidate builds ready, I hope that wxHaskell users will help out by trying to install the new version on as many machines as possible, so that we can be sure that we have everything working.

How does wxHaskell event handling work – part 1

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.

Working around the static libstdc++ restriction

I’ve been spending a bit of time, on and off, over the last few months thinking about one of the most annoying bugs in wxHaskell, and how to fix it: that wxHaskell is now unusable from within GHCi.

There are actually two separate bugs: the first, which prevents things from working at all, is that on Windows, there is no DLL version of libstdc++; the second is that you cannot restart a wxWidgets session in GHCi after the application has terminated.

The first bug is a consequence of the fact that, on Windows, GHC uses the MinGW compiler suite to do C/C++ compilation and all linking (including with Haskell objects). The version of MinGW included in GHC does not support dynamic linking of libstdc++.

When wxHaskell was fully cabalized, one of the changes was to replace a complex and fragile build system for the C++ coe in wxHaskell with a much simple cabal-based build. The consequence of this is that the C++ code and wxcore Haskell 0bject code reside in the same library, which means that wxcore needs to be linked against libstdc++.

For compiled binaries, this is not so much of a problem: the static libstdc++ is linked with the other code and all works well. However, in the GHCi case, it is fatal: GHCi doesn’t know how to statically link non-Haskell object code, although it can load DLLs. When you try to load load wxcore, GHCi complains that it cannot load stdc++ (Loading package wxcore-0.12.1.6 … <interactive>: stdc++: The specified module could not be found. can’t load .so/.DLL for stdc++ (addDLL: could not load DLL))

The second problem: inability to restart a GUI, is a consequence of wxWidgets using static destructors. These are only executed when the application fully terminates, which is not the case in a GHCi.

It turns out that a good solution to both of these problems is to repackage the C++ part of wxHaskell as a DLL. The reasons are:

  • The C++ code in the DLL needs to be linked with libstdc++, but this can be done at DLL link time. If libstdc++ is statically linked with the DLL, wxcore no longer needs to depend on linking to libstdc++ (only wxc.dll).
  • If the DLL is dynamically unloaded, the C++ static destructors will be executed (as far as I can tell, this should be true for Linux .so shared libraries as well as Windows DLLs). This means that so long as the wxc shared library is loaded at application start-up and unloaded at application termination, we should be able to correctly restart a GUI.
  • GHCi can load DLLs, so wxHaskell should become usable on GHCi again (since the libstdc++ requirement would have gone away).

The problem, and it has occupied quite a bit of my rather limited spare time, is that this requires quite a bit of re-architecture.

Changes to compile wxc as a DLL

This is probably the simplest part, since wxc was compiled as a DLL in an older version of the build system. The downside is that I would like to use cabal to do the building, since asking anyone to have prerequisites much beyond Haskell Platform greatly reduces the attractiveness of a library.

Changes to use dynamically loaded libraries.

Most usage of shared libraries is pseudo-static, that’s to say that the addresses of functions exported from the shared library are fixed-up when the application is loaded by the linker-loader. In my case, I want to do things fully dynamically. The outline code is fairly straightforward (caveat: this is outline code – I haven’t run it!).

On Windows, you need something like:

HINSTANCE dllHandle = LoadLibrary("wxc.dll");
...
if (dllHandle != NULL) {
  FnPtrType fnPtr = (FnPtrType) GetProcAddress(dllHandle, "functionName");
  if (fnPtr != NULL) {
    return fnPtr(params...);
  }
}
FreeLibrary(dllHandle);

Code in Unix-land is very similar indeed:

void* lib_handle = dlopen("path/to/libwxc.so", RTLD_LAZY);
...
if (lab_handle != NULL) {
  FnPtrType fnPtr = dlsym(lab_handle, "functionName");
  if ((error = dlerror()) == NULL) {
    return fnPtr(params...);
  }
}
...
dlclose(lib_handle);

The problem is that I have about 2,000 functions which need to be exported in this way, and the only sane approach will be to automate things. I also need to find the correct locations to load and unload libraries, and to ensure that all function pointers are invalidated when I unload the DLL.

I now have most of the work done on changes to the build system, and my ideas thought through for the dynamically loaded libraries. In the next few entries (which will hopefully be more closely spaced than of late), we’ll look at these in some more detail.

One aside: I mentioned doing some work to enable Swig to be used as a wrapper generator for the wxc wrapper layer. I still want to do this, but it has been put aside in the “too much work right now” pile. Doing things as I am planning is less maintainable in the long run, but stands a fighting chance of being finished sometime this decade, and I want people to use wxHaskell more than I need to have an awesome system for automating the generation of wrappers over C++ code.

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…

Wrapping optional components

In looking at this issue, I’m not really addressing my TODO list in the correct order, but Konstantin Chugalinskiy asked a question on the subject on the wxhaskell-users mailing list. His specific question was about using wxWebConnect to provide access to a WebKit browser component from wxHaskell, but the problem is more general.

An aspect of wxWidgets which has been problematic when it comes to maintenance is that there is actually a very considerable number of valid build configurations, and this continues to increase as time passes. When we moved to the Cabalized build system six months or so ago, one change which was a consequence is that we only wrap the ‘default’ subset of wxWidgets. This has the unfortunate side-effect that some features which were previously supported are no longer available. The main among these are:

  • OpenGL
  • Styled Text Control

I would prefer for these optional components to be wrapped up as separate Cabalized libraries (so if you want Styled Text Control support you would need to do something like ‘cabal install wxstc’), so started looking into the problem.

wxDirect is not a general purpose wrapper generator

The most obvious approach is to use wxdirect to generate the Haskell bindings, and this immediately causes a problem, which is this: wxdirect was really designed as a single purpose wrapper generator. It is very closely coupled to the C coding style used by the C wrappers for wxWidgets in wxHaskell, which are hand-written in a specific style. In addition, its internal design and outputs are really only intended to generate WxCore.

In the past, shelarcy made changes to wxdirect to enable it to generate Haskell bindings for the Styled Text Control, but these changes were made in a way which really supports STC only, and which still requires hand-written bindings.

Generating bindings automatically

At the moment, WxCore relies on a set of C language bindings to wxWidgets which need to be written and maintained manually. These work well, and have the considerable advantage of generally being very thin wrappers over the C++ code. The problem, however, as shown up in a couple of earlier postings to this blog, is that it is very easy for errors to creep in, and for new (or old) functions not to be wrapped.

About a year ago, on the wxhaskell-devel list there was some discussion on trying to generate the C bindings automatically. At the time, the discussions centred around using Doxygen to generate the information required to generate the C bindings. There was a proof of concept by Mads Lindstroem, using scripts written in Python to do much of the work. I have decided to see if I can make any progress in this direction (if it proves to be too much work, I’ll just make changes to wxDirect, but this will mean staying with hand-written C wrappers).

Looking at the Doxygen approach, one problem is that it seems to require a complete copy of the wxWidgets source repository (to get at the Doxygen sources) – plus I’d rather work in Haskell than Python (*) and I have never succeeded in getting *any* of the XML libraries for Haskell to work properly on my Windows development box.

(*) Nothing against Python – it is a fine language, but my spare time is very limited, and I prefer not to spend it re-learning a language I have mostly forgotten.

The most preferable solution would be to generate the wrapper code from the wxWidgets headers directly, since that would avoid transcription bugs and scale nicely to the problem of wrapping other wxWidgets components if needed. There are a couple of ways to approach this:

  • gccxml generates an XML representation of the parse tree of a piece of C++ code. It suffers from a couple of issues: I couldn’t get it to build on Windows, and I’d have to get an XML library for Haskell working on Windows. It also appears not to be very actively maintained.
  • SWIG is designed for exactly this sort of thing, but doesn’t support Haskell.

Now, SWIG support for Haskell would actually be pretty sweet, as it would make binding to many C++ libraries a great deal easier. I’m going to look into it – after all, how hard can it be? Probably the most significant issue to work through is that the WxCore API has been stable for a very long time now, so whatever approach to wrapping I take needs to come out with exactly what we have today (minus the bugs, obviously).

Building a text editor (Part 5)

In this section of the tutorial, we add find/replace functionality to the editor. Much of the required support is built-in to the textCtrl, but it is only exposed by the wxCore library, so we have a rather thin wrapper over the wxWidgets C++ code to work with.

Once more, we need to work around a couple of bugs in wxHaskell – the find/replace identifiers are defined incorrectly (or missing), so we hide the imported versions and replace them later on with the correct values. We also require a couple of extra pieces of library code.

> import Graphics.UI.WXCore hiding (wxID_CUT, wxID_COPY, wxID_PASTE,
>                                   wxID_FIND, wxID_FORWARD, wxID_BACKWARD)
> import Data.Bits
> import Data.Char (toLower)
> import Data.List

We maintain a copy of the user-selected find/replace options. The user can select: the search direction; whether the operations are case sensitive; whether to look for whole word matches only and whether to wrap around from the end of the document back to the start. We wrap all of this information in a data structure.

We also need to add an extra field to our GUICtx context state. This holds a FindReplaceData (), which is an object used by many of the functions associated with the textCtrl find/replace functionality exposed in wxCore. The internal details of FindReplaceData are opaque to the Haskell wrapper, but basically it is an object which maintains state on behalf of the find/replace dialog box.

> data FRFlags = FRFlags { frfGoingDown :: Bool,
>                          frfMatchCase :: Bool,
>                          frfWholeWord :: Bool,
>                          frfWrapSearch :: Bool
>                        }
>                deriving (Eq, Show)
> data GUIContext = GUICtx { guiWin    :: Frame (),
>                            guiEditor :: TextCtrl (),
>                            guiFile   :: Var (Maybe FilePath),
>                            guiTimer  :: TimerEx (),
>                            guiPast   :: Var [String],
>                            guiFuture :: Var [String],
>                            guiSearch :: FindReplaceData ()
>                          }

As mentioned earlier, some of the identifiers in Graphics.UI.WXCore.WxcDefs are incorrect:

> wxID_FIND, wxID_FORWARD, wxID_BACKWARD, wxID_REPLACE :: Id
> wxID_FIND       = 5035
> wxID_REPLACE    = 5038  -- This one is not in WxcDefs, so not hidden earlier
> wxID_FORWARD    = 5106
> wxID_BACKWARD   = 5107

Changes to the GUI function

As usual, a few changes are needed to the top-level GUI function (step5 in this case).

To make use of the find/replace dialog box, we create a FindReplaceData instance. This will be maintained in the GUI context state structure. In this case it is created with a default indicating that forward search (wxFR_DOWN) is selected. You could also use any of the following:

  • wxFR_WHOLEWORD to indicate that search/replace should only take place on whole words.
  • wxFR_MATCHCASE to indicate that case sensitive search/replace is selected.

As well as configuring when the FindReplaceData instance is created, you can use findReplaceDataSetFlags to do the same thing. If you choose to do this, please note that according to the wxWidgets documentation, these flags can only be changed before the find/replace dialog box is first shown. Once the dialog has been shown, the values selected in the dialog are used, and changes have no effect.

> future <- varCreate []
> search <- findReplaceDataCreate wxFR_DOWN
> let guiCtx = GUICtx win editor filePath refreshTimer past future search
> set editor [on keyboard := \_ -> restartTimer guiCtx >> propagateEvent]

We also need to add menu entries and menu event handlers. Since these have been covered extensively before, no need for further comment:

> menuAppend mnuEdit wxID_PASTE "&Paste\tCtrl-v" "Paste" False
> menuAppendSeparator mnuEdit
> menuAppend mnuEdit wxID_FIND "&Find...\tCtrl-f" "Find" False
> menuAppend mnuEdit wxID_FORWARD "Find &Next\tCtrl-g" "Find Next" False
> menuAppend mnuEdit wxID_BACKWARD "Find &Previous\tCtrl-Shift-g" "Find Previous" False
> menuAppend mnuEdit wxID_REPLACE "&Replace...\tCtrl-Shift-r" "Replace" False
> evtHandlerOnMenuCommand win wxID_PASTE $ paste guiCtx
> evtHandlerOnMenuCommand win wxID_FIND $ justFind guiCtx
> evtHandlerOnMenuCommand win wxID_FORWARD $ justFindNext guiCtx
> evtHandlerOnMenuCommand win wxID_BACKWARD $ justFindPrev guiCtx
> evtHandlerOnMenuCommand win wxID_REPLACE $ findReplace guiCtx
> set win [menuBar := [mnuFile, mnuEdit]]

Dialog box functions

The dialog boxes used for both search and replace are launched by the same function – it is simply the dialog box style which changes, along with the text at the top of the dialog box. We use a couple of helper functions to make this explicit, and to keep our menu event handler functions simple:

To open a ‘find’ dialog box:

> justFind guiCtx = openFindDialog guiCtx "Find..." dialogDefaultStyle

To open a ‘replace’ dialog box, we need to modify the dialog box style to indicate that a ‘replace’ dialog box is wanted. In wxWidgets, control styles are usually represented by integers, with various stylistic elements represented by setting (or not) particular bits in the style integer.

For the built-in standard dialog boxes, there is a value dialogDefaultStyle which represents the default stylistic attributes of the control. It can be updated by setting or clearing specific flags, which we do using functions from Data.Bits. I’m probably not teaching anyone anything new here (it’s binary operations 101), but just in case…

  • To set a bit denoted by ‘flag’, use the logical ‘OR’ function (this is (.|.) in Haskell), e.g value’ = value .|. flag
  • To clear a bit denoted by ‘flag’, use the logical ‘NOT’ and logical ‘AND’ functions (complement and (.&.) in Haskell), e.g. value’ = value .&. (complement flag)
> findReplace guiCtx = openFindDialog guiCtx "Find and Replace..."
>                    $  dialogDefaultStyle .|. wxFR_REPLACEDIALOG

Let’s look at the code to handle the dialog box itself. I’ll cover this in some detail, as many of the built-in wxHaskell dialogs require similar handling.

The first line is straightforward: create the find/replace dialog instance. It is not visible by default, so we can continue to configure it without things looking strange for the user. There are a few things to notice:

  • The dialog box is associated with a parent window – in this case the Frame inside which the editor instance sits.
  • The dialog box requires an instance of FindReplaceData – we use the one we created in the top level of the GUI, and saved in the GUI context state variable.
  • The dialog style we set in either findReplace or justFind is being modified further, disabling whole word search.

The next lines are more complex. The purpose is to set event handlers for each of the events which can be handled by the find/replace dialog.

There are a few things we need to get correct for this to work. The first problem is to ensure that the GUI context is passed to the event handlers as required (it is a curried parameter they expect to receive). The second is to ensure that the events are propagated to other windows even if they are processed by the find/replace dialog event handlers.

Let’s take a look at the windowOnEvent function. This is one area where a quick look at the wxWidgets documentation will not help you much. This is because event handling in wxWidgets is hidden under an opaque macro layer which hides much of the complexity from the C++ programmer, but doesn’t help the user of a language binding like wxHaskell very much.

The signature of windowOnEvent is:

windowOnEvent :: Window a -> [EventID] -> handler -> (Event () -> IO ()) -> IO ()
windowOnEvent window events state eventHandler

The parameters are:

  • window The window to this the event handler is attached – in this case our dialog box.
  • events A list of the event IDs to which the event handler will respond. A list is required here because the event handler may respond to multiple events.
  • state Any Haskell data the programmer wishes to associate with the event handler. This is often set to be the main event handler function, since it allows the event handler to be straightforwardly retrieved (there is a function unsafeGetHandlerState to do this if required).
  • eventHandler The user-provided function to handle the event.This must be of type Event () -> IO ().

Let’s step through what happens when calling windowOnEvent:

  1. windowOnEvent window events state eventHandler is a pseudonym for  windowOnEventEx window events state (\ownerDelete -> return ()) eventHandler. In other words, we are creating an event handler where we take on the responsibility for any clean-up required when the event handler is deleted. This is the most usual case (the garbage collector normally takes care of the rest), but it you need it, there is always windowOnEventEx where you provide your own clean-up function.
  2. Disconnect any existing event handlers, calling eventHandlerOnEventDisconnect. Please note (if you are planning on  using any multi-threading) that this call modifies non thread-safe global state.
  3. Create a closure containing state, a clean-up function and a function which is called when an event occurs.
  4. For each event in events, call evtHandlerConnect (a wrapper around the wxWidgets function wxEvtHandler::Connect()). The closure created in the previous step is passed as user data.

What all of this means in practice is that a call to windowOnEvent associates an event handler and a piece of user-provided data with a particular eventID on a given window.

Let’s look at the implementation, working back from usage:

  • A call to winSet wxEVT_COMMAND_FIND findNextButton associates the event wxEVT_COMMAND_FIND with the event handler findNextButton.
  • This is equivalent to let hnd _ = findNextButton guiCtx >> propagateEvent in windowOnEvent frdialog [wxEVT_COMMAND_FIND] hnd hnd
  • Which is equivalent to windowOnEvent frdialog [wxEVT_COMMAND_FIND] (findNextButton guiCtx >> propagateEvent) (findNextButton guiCtx >> propagateEvent)

Incidentally, another, possibly slightly clearer (and more verbose) way of writing effectively the same code is:

  • windowOnEvent [wxEVT_COMMAND_FIND] findNextHdlr (\evt -> findNextHdlr)
    where findNextHdlr = findNextButton guiCtx >> propagateEvent

Finally, set the dialog box to be visible, and we’re done.

> openFindDialog :: GUIContext -> String -> Int -> IO ()
> openFindDialog guiCtx@GUICtx{guiWin = win, guiSearch = search} title dlgStyle =
>  do
>    frdialog <- findReplaceDialogCreate win search title
>                         $ dlgStyle .|. wxFR_NOWHOLEWORD
>    let winSet k f = let hnd _ = f guiCtx >> propagateEvent
>    in  windowOnEvent frdialog [k] hnd hnd
>    winSet wxEVT_COMMAND_FIND findNextButton
>    winSet  wxEVT_COMMAND_FIND_NEXT findNextButton
>    winSet  wxEVT_COMMAND_FIND_REPLACE findReplaceButton
>    winSet  wxEVT_COMMAND_FIND_REPLACE_ALL findReplaceAllButton
>    set frdialog [visible := True]

Find next / previous

The functions to find the next and previous matches are very similar, and each is a wrapper around the findNextButton function.

In the case of justFindNext we retrieve the FindReplaceData and force the search direction to be downwards. For justFindPrev we set the search direction upwards.

> justFindNext guiCtx@GUICtx{guiSearch = search} =
>  do
>    curFlags <- findReplaceDataGetFlags search
>    findReplaceDataSetFlags search $ curFlags .|. wxFR_DOWN
>    findNextButton guiCtx
> justFindPrev guiCtx@GUICtx{guiSearch = search} =
>  do
>    curFlags <- findReplaceDataGetFlags search
>    findReplaceDataSetFlags search
>                        $ curFlags .&. complement wxFR_DOWN
>    findNextButton guiCtx

To make it easier to work with the flags in a FindReplaceData, we have an auxiliary data structure FRFlags (which was defined earlier) and a function buildFRFlags to construct the data structure from the flags in a FindReplaceData. Note also that there is an additional parameter in FRFlags which is not present in FindReplaceData. This indicates whether we should wrap our searches around the text buffer, and is set by a Bool parameter to buildFRFlags.

> buildFRFlags :: Bool  -> Int  -> IO FRFlags
> buildFRFlags w x =
>   return FRFlags {frfGoingDown = (x .&. wxFR_DOWN) /= 0,
>                   frfMatchCase = (x .&. wxFR_MATCHCASE) /= 0,
>                   frfWholeWord = (x .&. wxFR_WHOLEWORD) /= 0,
>                   frfWrapSearch = w}

The findNextButton function is responsible for finding the next occurrence of the search text, taking into account all of the user preferences. It is worth noting that this function can be called both when the find/replace dialog is opened, as a result of menu selections, or as a result of hot-key combinations being pressed. This works because we retain the FindReplaceData from the last time the find/replace menu was open at all times, and pass it around as a curried parameter.

This function is pretty much a template for most of the remaining find/replace functions.

We first get the search string and flags from the FindReplaceData. The findReplaceDataGetFindString function returns the search string (s) and findReplaceDataGetFlags returns the search/replace flags. The flags are piped into buildFRFlags to obtain the flags (fs). Note that in this case we are forcing the case that the user wishes to wrap the search around the text buffer.

The findMatch function returns the location (insertion point) at which the match was found. Since it is possible that there is no match, this is wrapped up in a Maybe.

If we successfully find a match, we set the insertion point to the start of the match, then select the matched text (we can do this because we know the length of the text matched). If no match is found, we pop up a dialog box – you might prefer to do nothing in this case!

> findNextButton guiCtx@GUICtx{guiEditor = editor, guiWin = win,
>                              guiSearch= search} =
>  do
>    s  <- findReplaceDataGetFindString search
>    fs <- findReplaceDataGetFlags search >>= buildFRFlags True
>    mip <- findMatch s fs editor
>    case mip of
>        Nothing -> infoDialog win "Find Results" $ s ++ " not found."
>        Just ip -> do
>                     textCtrlSetInsertionPoint editor ip
>                     textCtrlSetSelection editor ip (length s + ip)

The findReplaceButton function is similar in many respects to findNextButton, but now we need to worry about updating the GUI history.

> findReplaceButton guiCtx@GUICtx{guiEditor = editor, guiWin = win,
>                                 guiSearch = search} =
>  do
>    s <- findReplaceDataGetFindString search
>    r <- findReplaceDataGetReplaceString search
>    fs <- findReplaceDataGetFlags search >>= buildFRFlags True
>    mip <- findMatch s fs editor
>    case mip of
>        Nothing -> infoDialog win "Find Results" $ s ++ " not found."
>        Just ip -> do
>                     textCtrlReplace editor ip (length s + ip) r
>                     textCtrlSetInsertionPoint editor ip
>                     textCtrlSetSelection editor ip (length r + ip)
>                     updatePast guiCtx

The findReplaceAllButton function is a further development of findReplaceButton. One of the key changes is that we no longer wrap our searches, as this carries a risk of infinite loops. Instead, we explicitly set the insert point (using textCtrlSetInsertionPoint) to the start of the text before doing the replacement. This is essentially equivalent to performing a wrapped replace all.

The main work of replacing all instances falls to the auxiliary function replaceAllIn. This is the same piece of code as used to do text replacement in findReplaceButton except that it is called recursively until there are no further matches

> findReplaceAllButton guiCtx@GUICtx{guiEditor = editor,
>                                    guiSearch = search} =
>  do
>    s <- findReplaceDataGetFindString search
>    r <- findReplaceDataGetReplaceString search
>    fs <- findReplaceDataGetFlags search >>= buildFRFlags False
>    textCtrlSetInsertionPoint editor 0
>    replaceAllIn s r fs editor
>    updatePast guiCtx
>      where
>        replaceAllIn s r fs editor =
>         do
>           mip <- findMatch s fs editor
>           case mip of
>               Nothing -> return () -- we're done here
>               Just ip -> do
>                            textCtrlReplace editor ip (length s + ip) r
>                            textCtrlSetInsertionPoint editor $ length r + ip
>                            replaceAllIn s r fs editor

Matching text in a TextCtrl

The findMatch function looks for text which matches the search criteria, and returns the position of the first match or Nothing.

A TextCtrl models its contents as a string, and represents the insertion point as zero-indexed offset from the start of the string. It is worth noting that some criticism could be made of the memory usage of the implementation below – this would be valid for extremely large files (we are taking a copy of the text control contents when we call textCtrlGetInsertionPoint), but the wxWidgets text control is really only useful for relatively small files – up to around 64kB (this is a hard limit on some platforms). If you are interested in working on very large files, you will probably want to implement a custom control. In any case, the approach shown is quite reasonable for demonstration purposes, and for any reasonable use of TextCtrl!

If we are doing case-insensitive search, the simplest thing to do is to transform the search and replace strings to lower case. Note that this does not affect the text in the control (we are working on a copy) or the actual text we use to replace matches (we are also working with a copy!).

We use separate functions for searching forwards (nextMatch) and backwards (prevMatch). These each return both the position of the match in the string (which is the same as the insertion point in the text control) and an indication of whether they needed to wrap around to get a match (this will disallow a match if wrap was disabled).

> findMatch query flags editor =
>  do
>   txt <- get editor text
>   ip <- textCtrlGetInsertionPoint editor
>   let (substring, string) = if frfMatchCase flags
>                             then (query, txt)
>                             else (map toLower query, map toLower txt)
>   funct = if frfGoingDown flags
>           then nextMatch (ip + 1)
>           else prevMatch ip
>   (mip, wrapped) = funct substring string
>   return $ if (not $ frfWrapSearch flags) && wrapped
>            then Nothing
>            else mip

The prevMatch and nextMatch functions are pretty similar.

The base case for prevMatch is that you are looking for an empty string. The most logical thing to do when looking for nothing is to find nothing, so:

> prevMatch _ [] _ = (Nothing, True)

The first condition in prevMatch for other cases is a check that we have not wrapped around from start of string. If we have (or would during the search) then we restart the search from then end of the string.

Otherwise, we use the nextMatch function to find our matches. To use nextMatch when we are supposed to be going backwards requires us to reverse both the substring and the string being searched, as well as changing the insert point to reflect position from the end of the string, rather than the start.

> prevMatch from substring string
>     | length string < from || from <= 0 =
>           prevMatch (length string) substring string
>     | otherwise =
>     case nextMatch (fromBack from)
>                    (reverse substring) (reverse string) of
>         (Nothing, wrapped) ->
>             (Nothing, wrapped)
>         (Just ri, wrapped) ->
>             (Just $ fromBack (ri + length substring), wrapped)
>       where
>         fromBack x = length string - x

The base case for nextMatch is exactly the same as for prevMatch, and for the same reasons.

> nextMatch _ [] _ = (Nothing, True)

The first condition covers the case where the substring is longer than the string. No search can every succeed in this case, and the search would wrap.

The second condition covers the case where searching would cause wrap-around, and we restart from the beginning of the string.

The third (normal) case works as follows:

  • Drop the characters before the (current) insertion point. If we find a match here, then we successfully matched without wrapping around the text.
  • Take all of the characters before the insertion point and further characters up to the length of the substring. If we find a match hers, then we successfully matched, but needed to wrap around to do so.
> nextMatch from substring string
>     | length substring > length string = (Nothing, True)
>     | length string <= from = nextMatch 0 substring string
>     | otherwise =
>   let after = drop from string
>       before = take (from + length substring) string
>       aIndex = indexOf substring after
>       bIndex = indexOf substring before
>   in case aIndex of
>          Just ai -> (Just $ from + ai,  False)
>          Nothing -> case bIndex of
>                         Nothing -> (Nothing, True)
>                         Just bi -> (Just bi, True)

The indexOf function finds the location of a given substring in a string.

> indexOf substring string = findIndex (isPrefixOf substring) $ tails string

Building a text editor (Part 4)

In this very short post we add copy/cut/paste support to the editor. These will be accessible from the menu as well as by using the usual CUA keystrokes (Ctrl-C, Ctrl-X, Ctrl-V).

Most of the required functionality is provided by the TextCtrl itself, so the code mainly consists of connecting up event handlers to bind to the TextCtrl functions.

The first issue we need to deal with is a wxHaskell bug – this will be fixed shortly, but in the meantime it is worth knowing about. The problem is that wxHaskellprovides the wrong constants for wxID_CUT, wxID_COPY and wxID_PASTE, which are the standard identifiers used by the text control.

As always, changed text id highlighted in red.

 > import Graphics.UI.WXCore hiding (wxID_CUT, wxID_COPY, wxID_PASTE)

We can now put the correct values in ourselves. These are ‘magic’ numbers, and you’ll have to trust me that they are correct – it is not particularly easy to work out the correct values without wading through lots of C++ code.

 > wxID_MYUNDO, wxID_MYREDO, wxID_CUT, wxID_COPY, wxID_PASTE :: Id
 > wxID_MYUNDO = 5107
 > wxID_MYREDO = 5108
 > wxID_CUT    = 5031
 > wxID_COPY   = 5032
 > wxID_PASTE  = 5033

We now add the menu items and their event handlers. Nothing new here: we’ve looked at menus before. These go into the GUI top level function, along with the other UI component definitions.

 > menuAppendSeparator mnuEdit
 > menuAppend mnuEdit wxID_CUT "C&ut\tCtrl-x" "Cut" False
 > menuAppend mnuEdit wxID_COPY "&Copy\tCtrl-c" "Copy" False
 > menuAppend mnuEdit wxID_PASTE "&Paste\tCtrl-v" "Paste" False

 > evtHandlerOnMenuCommand win wxID_CUT $ cut guiCtx
 > evtHandlerOnMenuCommand win wxID_COPY $ copy guiCtx
 > evtHandlerOnMenuCommand win wxID_PASTE $ paste guiCtx

We also require three new event handler functions:

— We just copy the selected text

The copy function simply uses the textCtrlCopy function provided by the control. Remember (see part 2) that we use GUICtx as a way to pass around the global state (the GUI widgets).

 > copy GUICtx{guiEditor = editor} =
 >   textCtrlCopy editor

The cut function also uses the standard editor functionality, but in this case we are modifying the contents of the TextCtrl and we must update the GUI with an undo action.

 > cut guiCtx@GUICtx{guiEditor = editor} =
 >   textCtrlCut editor >> updatePast guiCtx

The paste function also modifies the undo history.

 > paste guiCtx@GUICtx{guiEditor = editor} =
 >   textCtrlPaste editor >> updatePast guiCtx

Building a text editor (Part 3)

(Update: The original version of this installment included code which caused a memory leak. Fernando has fixed the code, and I recommend that you look at the latest version of wxhnotepad (cabal fetch wxhnotepad). I have updated the tutorial text below.)

This is the third installment of a series looking at wxhNotepad, a wxHaskell application written by Fernando Benavides, and this is where things start to become more interesting as we are adding some real functionality – in this case undo and redo functionality.

The wxHaskell TextCtrl () does, in fact, contain undo / redo support. It is not really ideal for a text editor, however, since it allows ‘undo’ on a new text file, for example.

GUI top level

The first thing to do is to extend the GUIContext type to maintain undo and redo history, each of which we express as a list of strings. There is also a TimerEx (), and this requires a little more explanation. The details will become clearer as we work through the code, but basically we use the timer as a means to help us to group ‘undoable’ and ‘redoable’ actions into larger units than characters.

As in the previous post, new code is highlighted in red.

> data GUIContext = GUICtx { guiWin    :: Frame (),
> guiEditor :: TextCtrl (),
> guiFile   :: Var (Maybe FilePath),
> guiTimer  :: TimerEx (),       -- ^ A timer to detect user actions
> guiPast   :: Var [String],     -- ^ For Undo history
> guiFuture :: Var [String]      -- ^ For Redo history
> }

Because we are not using the built-in undo/redo functionality in the TextCtrl, we need to define some unique menu identifiers. These must not conflict with identifiers in use elsewhere in the application. The numbers used are ‘magic’ – there is currently no convenient way to ensure that there is no such conflict – something which should probably be fixed in a future release of wxHaskell.

> wxID_MYUNDO, wxID_MYREDO :: Id
> wxID_MYUNDO = 5107
> wxID_MYREDO = 5108

Most of the changes to the top level GUI function require little comment as we have discussed the details in earlier installments.

> step3 :: IO ()
> step3 =
>   do
>     win <- frame [text := "wxhNotepad - Step 3", visible := False]
>     editor <- textCtrl win [font := fontFixed,
>                             text := "Undo / Redo support is ready by " ++
>                                     "default on text controls..."]
>     filePath <- varCreate Nothing

We create a timer instance in much the same way as any wxHaskell control. The timer interval is set to 1000 seconds (10^6ms).

The timer is used in an interesting way. Each time a keyboard event is received, restartTimer is called before sending the keyboard event on to the TextCtrl (we do this with the call to propagateEvent, which passes any interecpted event on to other controls which might wish to respond to it). In restartTimer a shorter (1 sec) timer interval is used to group input into chunks a little longer than a single character, so that undo and redo work on more meaningful chunks of text.

Once the timer has been created, we call updatePast to begin recording the undo/redo stream.

>     refreshTimer <- timer win []
>     past <- varCreate []
>     future <- varCreate []
>     let guiCtx = GUICtx win editor filePath refreshTimer past future
>     timerOnCommand refreshTimer $ updatePast guiCtx
>     set editor [on keyboard := \_ -> restartTimer guiCtx >> propagateEvent]
>     updatePast guiCtx

The remainder of the changes in the GUI top level relate to the extra menu entries to support undo and redo. These follow the pattern described in the previous post.

>     mnuFile <- menuPane [text := "File"]
>     mnuEdit <- menuPane [text := "Edit"]
>     menuAppend mnuFile wxID_OPEN "&Open...\tCtrl-o" "Open Page" False
>     menuAppend mnuFile wxID_SAVE "&Save\tCtrl-s" "Save Page" False
>     menuAppend mnuFile wxID_SAVEAS "Save &as...\tCtrl-Shift-s" "Save Page as" False
>     menuAppend mnuFile wxID_CLOSE "&Close\tCtrl-W" "Close Page" False
>     menuAppend mnuEdit wxID_MYUNDO "&Undo\tCtrl-z" "Undo last action" False
>     menuAppend mnuEdit wxID_MYREDO "&Redo\tCtrl-Shift-z" "Redo last undone action" False
>     evtHandlerOnMenuCommand win wxID_OPEN $ openPage guiCtx
>     evtHandlerOnMenuCommand win wxID_SAVE $ savePage guiCtx
>     evtHandlerOnMenuCommand win wxID_SAVEAS $ savePageAs guiCtx
>     evtHandlerOnMenuCommand win wxID_CLOSE $ windowClose win False >> return ()
>     evtHandlerOnMenuCommand win wxID_MYUNDO $ undo guiCtx
>     evtHandlerOnMenuCommand win wxID_MYREDO $ redo guiCtx
>     set win [menuBar := [mnuFile, mnuEdit]]
>     set win [layout := fill $ widget editor,
>              clientSize := sz 640 480]
>     focusOn editor
>     set win [visible := True]

File Handling Events

The changes to the file handling are very minimal – just a couple of lines in openPage so that the undo history is cleared when a new file is opened, and then populated with the initial file text (since this represents the first state of the ‘undo’ action list).

> openPage guiCtx@GUICtx{guiWin = win, guiEditor = editor, guiFile = filePath} =
>   do
>   maybePath <- fileOpenDialog win True True "Open file..."
>                               [("Haskells (*.hs)",["*.hs"]),
>                                ("Texts (*.txt)", ["*.txt"]),
>                                ("Any file (*.*)",["*.*"])]
>                               "" ""
>   case maybePath of
>     Nothing   -> return ()
>     Just path -> do
>                  clearPast guiCtx
>                  textCtrlLoadFile editor path
>                  updatePast guiCtx
>                  set win [text := "wxhnotepad - " ++ path]
>                  varSet filePath $ Just path

There is a new event handler for the ‘undo’ menu item.

> undo guiCtx@GUICtx{guiEditor = editor, guiPast = past, guiFuture = future} =
>   do
>     -- Now we try to detect if there's something new and kill the timer
>     updatePast guiCtx
>     history <- varGet past
>     case history of
>       []            -> return () -- Nothing to be undone
>       [t]           -> return () -- Just the initial state, nothing to be undone
>       tnow:tlast:ts -> do
>                          -- We add an action to be done on redo
>                          varUpdate future (tnow:)
>                          -- we remove it from the history
>                          varSet past $ tlast:ts
>                          -- and set the editor accordingly
>                          set editor [text := tlast]

The ‘redo’ menu item is handled similarly…

> redo guiCtx@GUICtx{guiEditor = editor, guiPast = past, guiFuture = future} =
>   do
>     -- First of all, we try to detect if there's something new
>     updatePast guiCtx
>     coming <- varGet future
>     case coming of
>       []   -> return () -- Nothing to be redone
>       t:ts -> do
>         -- remove it from the coming actions
>         varSet future ts
>         -- move it to the history actions
>         varUpdate past (t:)
>         -- and set the editor accordingly
>         set editor [text := t]

The updatePast function adds a new event to the undo history list. When updatePast is called, both the history and current editor text (tnow) are fetched and compared. If the head of the history list is not the same as the editor text, record the change.

> updatePast guiCtx@GUICtx{guiEditor = editor, guiPast = past, guiFuture = future} =
>  do
>    tnow    <- get editor text
>    history <- varGet past
>    case history of
>      []  -> varSet past [tnow]
>      t:_ -> if t /= tnow -- if there was a real change, we recorded it
>             then do
>               varUpdate past (tnow:)
>               varSet future [] -- we clean the future because the user is writing a new one
>             else     -- otherwise we ignore it
>               return ()
>               -- Now that history is up to date, we let the timer rest until new activity
>               -- is detected
>               killTimer guiCtx

The clearPast function clears the undo (past) and redo(future) lists.

> clearPast GUICtx{guiPast = past, guiFuture = future} =
>  do
>    varSet past []
>    varSet future []

The restartTimer function is used as part of a mechanism to try to separate undo/redo information into useful chunks instead of individual characters. This is done by starting a short duration (1 sec) timer which gets reset each time there is input from the user. The result is that we have a ‘timer’ which times out whenever the user pauses typing for more than a second. This is called from the textCtrl keyboard event handler, and allows us to group keyboard input according to natural pauses in typing.

> restartTimer guiCtx@GUICtx{guiWin = win, guiTimer = refreshTimer} =
>  do
>    started <- timerStart refreshTimer 1000 True
>    if started
>        then return ()
>        else do
>           errorDialog win "Error" "Can't start more timers"
>           wxcAppExit

The killTimer function starts a new timer of very long duration (something around 20 minutes). Should the timer expire, no action is taken, as the purpose here is simply to wait for some keyboard activity.

> killTimer GUICtx{guiTimer = refreshTimer} = timerStop refreshTimer

Note: there is a memory leak in this code

Ever expanding TODO lists

There was a short discussion on the wxhaskell-users list yesterday about a fairly long outstanding wxHaskell bug: the fact that you cannot restart a wxHaskell GUI from within GHCi.

This post is not about that bug, but about a comment from Eric Kow:

 people are actually quite understanding about things being done slowly if
 they have a clear idea where they're headed, ie. what's being prioritised,
 what's not; what things are blocked on, etc.

Eric is quite right, of course, so this post is a quick dump of my wxHaskell TODO list. Sadly, at the moment I seem to be the only person doing any wxHaskell core development (though I’d like to namecheck some great help from Brian Lewis on Cabalizing wxHaskell a couple of months back), so it is probably the wxHaskell TODO list. The list is in approximate priority order, although I reserve the right to change it unilaterally if I find I have an urgent need for something!

  1. Update wxHaskell to work with GHC 6.12.1 once the next Haskell Platform is released (I don’t have the time to go putting together a complete platform myself from source, so I’ll wait on the Platform)
  2. Finish the series on wxhNotepad. I see this as extremely important in helping wxHaskell beginners to get going. The next two posts are mostly finished, and you should see them soon.
  3. Solve the problem (a consequence of Cabalization) that, at least on Mac and Windows, it is no longer possible to use wxHaskell at all in GHCi due to a dependency on libstdc++ (which exists only as a static library on Mac and Windows). This is really a GHC/GHCi bug, but we need to deal with the consequences.
  4. Write improved documentation and/or blog articles on specific aspects of wxHaskell. Layout is a strong contender for more work.
  5. Investigate why wxHaskell cannot be reloaded in GHCi. Actually, I have a pretty good idea what the problem is. The wxWidgets library uses C++ static constructors and destructors in a few key places. When wxHaskell returns, what it actually does is to return from wxApp::OnInit(). Unfortunately, on return from this function, some of the internal structures of wxWidgets are corrupted and this can only be resolved by ensuring that the static destructors are executed and that the static constructors are re-run. I believe that the solution will probably involve dynamically unloading the wxWidgets library and reloading it. Since this is something which is almost completely OS-dependent, it will require me immersing myself in arcane details of dynamic linking for Windows, Linux and Mac to get it working.
  6. Get the Styled Text Control back into wxHaskell, but now as a separate (Cabal installable) module. This will act as a demonstration/tutorial for bringing any wxWidgets component which can be compiled as a separate library into wxHaskell
  7. Look at the list of bugs – both in the buglist and those accumulated in wxhaskell-users
  8. Write a guide to make it easier for others to contribute to wxHaskell. I’m well aware that the amount of effort I can contribute to the project is not really enough to keep it thriving, and I’d like to lower the barrier to entry.

I find it slightly sad that the Haskell community doesn’t really seem able to support even one full-featured GUI binding (from what I can see, Gtk2hs development is also very slow at the moment). It’s not very glamorous work, but I believe it is an important part of making Haskell useful to a wider range of people.