A cross-platform crystallographic graphical interface with wxWindows

Richard Cooper

Chemical Crystallography Lab, 9 Parks Road, Oxford, OX1 3PD,UK.
E-mail:
richard.cooper@chem.ox.ac.uk and WWW: http://www.xtl.ox.ac.uk/

CRYSTALS (a single crystal diffraction refinement and analysis software package), and Cameron (software for production of crystallographic diagrams) are both maintained by staff in the University of Oxford's Chemical Crystallography Laboratory. Both programs have had a graphical user interface added in order to take advantage of the facilities available in a windowed environment. This work was carried out as a student project, and a Microsoft Windows version of CRYSTALS and Cameron was released into the world in August 1999. The GUI was developed in C++ using classes from the Microsoft Foundation Classes (MFC) and it is currently being ported to wxWindows (http://www.wxwindows.org/) with the aim of releasing Linux and Macintosh versions.

[Screenshot of CRYSTALS software]

 

Figure 1: CRYSTALS running under X on Linux using the wxWindows library.

Toolkits and libraries

There are three mature, free or free-ish, cross-platform C++ GUI class libraries available that are being actively developed and supported: wxWindows, Qt and V.

wxWindows is a C++ class library that allows developers to create graphical C++ programs using a common API across a range of different platforms (most importantly for us: Linux, Windows and Macintosh). It is distributed with source code under a modified L-GPL licence, meaning that developers can distribute software which uses the library without having to release source code resulting in an active user base of academics, commercial companies and open-sourcers. Rather than providing the lowest-common-denominator across all platforms, wxWindows often re-implements controls that are missing in one platform and provides access to platform specific functionality where it might reasonably be required (e.g. Taskbar icons under Windows). As a result it has a rich set of GUI classes comparable to Borland's OWL or Microsoft's MFC.

V (http://vgui.sourceforge.net/) is very similar in ambition to wxWindows but seems to be a less active project with fewer developers and users.

Qt is also a cross-platform C++ toolkit (http://www.trolltech.com/) and like wxWindows it uses the ‘native look-and-feel' for the user interface on each platform. Event tables are replaced with a signal-slot mechanism with the result that programs require pre-compilation with a meta-object compiler to produce valid C++. The Unix library version is GPL'd and is used by the KDE desktop environment for UNIX. However the free Windows version lags well behind the current commercial release and the licensing is restrictive (it is only free if developing open-source software, i.e. not LGPL) putting it beyond the use of many academic developers who have neither permission to GPL their code, nor funding to purchase commercial licenses for graphical interface libraries (but that's another argument).

Where wxWindows is most impressive is in its support for a wide variety of compilers and environments – on Windows alone the library and applications can be built with no less than 8 different compilers, including the free cygwin and mingw32 compilers and even some old 16-bit compilers running on Windows 3.1! On UNIX it will happily link against the GTK or Motif/Lesstif libraries. The developers resist moves to use new C++ language features, such as templates and exceptions, as this would make the library less portable. This level of commitment to compatibility is extremely reassuring for the future maintenance of your software.

Why not use Java or Python? There is a lot of non-platform specific CRYSTALS code already in C++ and a huge amount of crystallographic Fortran code, a third language would require us to either port the C++ or give us a headache trying to compile and link three languages!

Crystals original GUI design

The usual way to use wxWindows (or MFC for that matter) is to treat it as a framework that provides the application with all the services it requires (graphical interface, string and file handling etc) and also runs the program for you (start-up, event handling). We have attempted to eschew this pervasive reliance on a single product, and instead just use the wxWindows library where it is required (graphics and events).

The graphical user interface for CRYSTALS was originally designed with portability in mind: a Macintosh version was developed in parallel and though never released it ensured that the code was well structured for cross platform development.

CRYSTALS and its GUI, as implemented using the MFC class library is a many layered thing. It may be divided into four layers with the science going on in the bottom layers (1 and 2) and the user interacting with the top layer (4):

  1. At the lowest level are the core crystallographic functions – accessed by instructions for manipulating and storing data (e.g. #RESTRAIN / DIST 1.54,0.01=C(1) TO C(2) / END). Such instructions may be issued by the user if they know what they are doing and/or have read the manual.
  2. A macro programming language, SCRIPTS, for: interacting with the user, analysing data, making decisions and constructing low-level instructions on behalf of the user. SCRIPTS make it easy to use the program without knowing the syntax, as all required information can be prompted for. SCRIPT macros are stored in text files and have a PASCAL-like syntax so they can be altered by the clued-up user.
  3. A GUI manager. Controls all user input and output of the program. As well as simple text I/O the GUI manager parses commands from the lower levels to create, layout and display dialog boxes and windows (Figure 2). The language for defining GUI objects has no decision making capabilities, but it can be issued from the SCRIPT macros, thereby allowing detailed control over what is displayed. Events occurring in the GUI (e.g. button presses, menu selection, text entered) are passed down to the lower levels where they will be processed as SCRIPT input or straight low-level instructions depending on the current state of the program.
  4. The GUI itself consists of controls (buttons, checkboxes, drawing areas, edit fields, menus etc.) each of which is derived from a platform specific class (e.g. CButton in MFC). A portable API is maintained by insisting that only added functions of the derived class may be called. For example, we may not directly call the SetWindowText(char *) member function of a derived CButton, but instead provide a generic public function SetText(char *) which does the same job, but will be the same on all platforms. Following this design isolates all platform specific code in the derived object classes, and porting the application should simply be a matter deriving a full set of GUI classes.

The creation, layout and management of windows and dialog boxes from the SCRIPT language gives us a tool which allows rapid development and testing of ideas by anyone using the program – SCRIPTS are interpreted at run-time so no recompilation is required.

[running under Redhat Linux]  [running under Microsoft Windows]

 

Figure 2: Layout code provides portability – Redhat Linux (left), Windows XP (right)

Porting from MFC

It must be emphasized that while we could immerse ourselves in the wealth of wxWindows facilities – free tools for designing dialog boxes, automatic layout of controls, and platform independent thread, file, drag-and-drop and network functions – this is not the approach we have taken. Instead we are currently regarding wxWindows as just another platform providing derivable C++ classes alongside the working Windows MFC and never-finished Mac platforms. This approach is important to keep levels 1-3 of the code platform and library independent. Clean separation of different levels is a common philosophy in CRYSTALS and one which should ultimately payoff in terms of maintainability: presently the GUI (levels 3 and 4) can be stripped off completely and a working command line version compiled with a few keystrokes. Similarly the SCRIPT parser (level 2) can be discarded with no effect on the crystallographic core of the program.

If you are porting an entire MFC framework application to wxWindows the recommended path is to recreate the applications user interface using available tools, and then transfer the guts of the code across from MFC to wxWindows. It is not recommended to try to run the two frameworks at once.

Upon perusing the wxWindows documentation (which, for a free software project, is some of the most complete and helpful ever found) the resemblance of the class hierarchy to MFC is immediately noticeable. Instead of CWinApp, we have wxApp, wxFrame instead of CFrameWnd, wxButton instead of CButton, and even wxPaintDC and wxMemoryDC in place of their MFC device-context cousins. Names of member functions and the order of parameters differ slightly, but the overall design is very similar. For instance to create a new instance of an edit control in each the two libraries:

    MFC:

            CEdit* myEd = new CEdit();

            myEd->Create(style, sizeRect, parent, id)

    wxWindows:   

            wxTextCtrl* myEd = new wxTextCtrl();

            myEd->Create(parent, id, label, position, size);

and the event handling of the two frameworks using macros is analogous:

    MFC:

            BEGIN_MESSAGE_MAP( DerivedEditBox, CEdit )

            ON_WM_CHAR()

            ON_WM_KEYDOWN()

            END_MESSAGE_MAP()

    wxWindows:

            BEGIN_EVENT_TABLE( DerivedEditBox, wxTextCtrl )

             EVT_CHAR( DerivedEditBox::OnChar )

             EVT_KEY_DOWN( DerivedEditBox::OnKeyDown )

            END_EVENT_TABLE()

MFC wraps the Win32 API very thinly in places, with the result that you must often understand the underlying Win32 to write code using MFC calls. In these cases wxWindows actually makes functions easier to use. The following fragments of code return the height, in pixels, of some text in a control. In the wxWindows version there is no need to know about device context handles or make Win32 API calls:

    MFC:

            CString text;

            SIZE size;

            HDC hdc= (HDC) (GetDC()->m_hAttribDC);

            GetWindowText(text);

            GetTextExtentPoint32(hdc, text, text.GetLength(),&size);

            return (size.cy+5);

    wxWindows:

            int cx,cy;

            GetTextExtent(GetLabel(), &cx, &cy );

            return (cy+5);

In a similar vein, one of the most useful wrappers is wxGLCanvas. OpenGL initialisation, requiring PixelFormatDescriptors and GLContexts, is thankfully hidden away – you simply create the wxGLCanvas and start issuing OpenGL commands.

Gotchas

Though the documentation is excellent for the mature core of the library, some newer classes, like wxGLCanvas are not so well documented. In these cases, the next best source of information is probably the source codes in the demo and samples subdirectories – there are plenty – find an example that uses the class of interest and see how it works. Failing that, the advantage of having the source is that you can step through the library code itself to see what is going on.

It is always worth compiling and testing things in the samples directory when you first install wxWindows – it can save you hours of hacking at your own code in vain only to later find that nothing compiles anyway because something is setup incorrectly.

From release to release some member functions may change slightly. This shifting API is the downside of not using a commercially supported product, such as Qt, where stability is considered much more important. Nevertheless, the changes are never large, and as it is now over ten years old, the underlying design of wxWindows is fairly well established and will not change that much.

A problem that may crop up from time-to-time is actually due to the similar style of MFC and wxWindows: it is easy to assume that you know what a member function does because it has a similar name. For example, MFC's CString::Format function is a member function which will set the text of the CString object: str.Format("%d",123); while wxWindows' Format function is static and returns a wxString, so you would say instead: str = wxString::Format("%d",123); Tread carefully.

Summary

In summary, wxWindows is similar to MFC offering a similar class hierarchy, event tables, and even things like device contexts for drawing on. In the case of CRYSTALS this was enough to make porting our MFC application to Linux fairly painless. Where the two libraries diverge, it is usually to the advantage of wxWindows – smaller executables when statically linked, simpler calls, and most importantly of all cross-platform support!

Web Links

WxWindows: http://www.wxwindows.org/

CRYSTALS: http://www.xtl.ox.ac.uk/crystals.html

List of free GUI toolkits (not just C++): http://www.atai.org/guitool/

Porting MFC Applications to Linux: http://www-106.ibm.com/developerworks/library/l-mfc/?n-l-4182

Acknowledgements

The initial design and implementation of the GUI and layout manager was carried out under the supervision of David Watkin in the Chemical Crystallography Laboratory, University of Oxford and in collaboration with Ludwig Macko and Markus Neuburger at the University of Basel, Switzerland.

The CCDC funded the DPhil project during which the initial GUI was developed.

EPSRC grant GR/N06830/01 funded the project Crystallography for the Next Generation.