Original URL: https://www.theregister.com/2006/12/18/juce_cross_platform/

Giving some Juce to cross-platform tools

Juce in the spotlight

By Dave Jewell

Posted in Software, 18th December 2006 18:20 GMT

Hands on Last month I looked at Qt, the popular C++ cross-platform framework which underpins the KDE desktop, and a whole lot more. This time, I'm continuing that same theme by taking a look at another cross-platform C++ library called Juce (OK, no jokes about Apple Juce!)

Chances are pretty good that you've never actually heard of Juce: I certainly hadn't until Reg Developer reader Iain McGuire pointed me in the right direction. I downloaded the demo and was immediately smitten. Nice one, Iain!

Juce overview

Perhaps the most remarkable thing about Juce is that it's the work of one talented developer – Julian Storer. According to Wikipedia (see entry for "Tracktion"), the Juce C++ class library arose out of the work that Julian put into the development of his popular digital audio production suite. More on Tracktion here, but you can download Juce from the official website.

The library supports development on Mac OS X, Windows and Linux. It's currently at version 1.4, and you can freely download the complete source code tree from the website (around 2.5 MBytes). Don't bother looking for platform-specific source code downloads because there aren't any; all three platforms are integrated within the same source tree – more on that later. If you're not ready to start delving around in Julian's source code, and would rather get a feel for what Juce can do, there are pre-built binaries (for Mac and Windows) of a very impressive demo program which shows some of the library's main capabilities.

If you download the Win32 version of the demo, you might be surprised to discover that the EXE file tips the scales at a sylphlike 751 KBytes. That's quite impressive for a stand-alone program developed with a cross-platform library and no DLL dependencies. However, inquisitive bugger that I am, a quick sniff with HexEdit instantly revealed that Mr Storer had used the UPX compressor to scrunch the demo executable. Even so, at around 1.7 Mbytes (the uncompressed size), I'm still impressed. Static builds with other cross-platform libraries are often very much larger.

Screenshot demonstrating transparency, image rotation, and the ability to work with SVG graphics.

The most negative thing I can say about the demo is that I didn't like the washed-out pastel colour scheme, although I don't like XP's "Fisher-Price" look either; I'm hard to please where user interface aesthetics are concerned (check out Figure 1 and notice the subtle shadowing under the disk drive). What you will find, if you've got Windows and a Mac, is that the program looks identical when running on both platforms. The reason for this – as discerning readers will suspect by scrutinising the screenshots – is that Juce doesn't use the native widgets provided by each platform (a widget, in the context of cross-platform development, is a user interface control). Instead, the implementation of each widget is built right into the Juce library.

As already stated, this has the advantage of giving you identical results on each platform, and the further advantage that you're not at the mercy of whatever versioning vagaries Microsoft might plonk into the next iteration of (e.g.) the Common Controls DLL. On the negative side, the punters might not like the Juce equivalent of the control's they're familiar with. Then again, because full source code is included, you're free to tinker with the look and feel as much as you want. Most of the Juce widgets adopt a somewhat OS X-like look and feel - most notably push buttons.

You'll see that even the window frame is rendered by the Juce library. If this is a bit too outré for your target audience, there's an option to render using the native windowing system.

Note: Well actually, there's a small problem here: if you run the Juce demo under OS X, bring up a drop-down menu and then click on and drag away the native window frame, the menu will remain onscreen and get "left behind" rather than collapsing as it should. This bug doesn't affect the Win32 port (The Juce website proclaims that the number of known bugs is always zero because as soon as Julian hears about a bug, he fixes it immediately. Go to it, Julian!).

Pressing some Juce...

As I mentioned earlier, the entire source tree of Juce is available for download. Rather than splitting the class library up into separate packages for each platform, the corresponding makefiles (or equivalent) are included within the tree. Below the /juce/build directory, you'll find individual subdirectories for Linux, OS X and Win32. The Linux directory contains a standard makefile and the build is compatible with GCC 3.3 onwards. The OS X subfolder contains a project file for Apple's XCode IDE, and under Windows there are separate solution files for Visual C++ 6.0 and 8.0 (a.k.a. Visual C++ 2005).

In practice, Visual C++ 2005 didn't seem to like the provided solution file. Taking a hint from the very active discussion forum, I switched over to Visual C++ 2005 Express Edition, and everything was fine from then on. In addition, the consensus seems to be that things will go a lot more smoothly if you avoid DLL builds on the Win32 platform. No surprises there then...

The number of platform-specific source files is small and localised within the source tree – just as it should be. The Win32 code is obviously sitting on top of the "barefoot" Win32 API, while the Linux-specific routines map down to X. On the Mac, the windowing system is implemented via Carbon. The bottom line in all three cases is that no additional runtime support is required by Juce applications. Again, just as it should be.

Once you peruse the demo program and library source code, you'll realise just how juicy Juce is! In addition to all the usual user interface widgets such as buttons, sliders, tabbed dialogs, list-boxes, tree-views, and so on, Juce also has built in support for multi-threading, XML parsing, OpenGL rendering, graphics paths, transparency, transforms, drag and drop support, and can even render PNG, JPG, SVG graphics, etc. You get file open/save dialogs (though you can use the native ones if preferred), directory browsers and a colour selector dialog. Heck – you can even have animated menu items...if you must!

What impresses me as much as anything is the elegance and conciseness with which even complex effects can be achieved. As an example, here's an abbreviated source snippet from the demo program. This code draws four star-shaped buttons on a form and sets them up as radio-buttons – see the "radio buttons" page in the Widgets section of the demo. And no, I'm not actually advocating the use of star-shaped radio buttons!

for (i = 0; i < 4; ++i) 

  {
   DrawablePath normal, over;   
 
   Path p;
   p.addStar (0.0f, 0.0f, i + 5, 20.0f, 50.0f, -0.2f); 
   normal.setPath (p);  
   normal.setSolidFill (Colours::lightblue);  
   normal.setOutline (4.0f, Colours::black);  

   over.setPath (p);  
   over.setSolidFill (Colours::blue); 
   over.setOutline (4.0f, Colours::black); 

    DrawableButton* db = new DrawableButton (String (i + 5) + T(" points"),
 DrawableButton::ImageAboveTextLabel);  
  db->setImages (&normal, &over, 0);   

  page->addAndMakeVisible (db); 
  db->setClickingTogglesState (true);  
  db->setRadioGroupId (23456);  

  const int buttonSize = 50;  
  db->setBounds (25 + i * buttonSize, 180, buttonSize, buttonSize); 

  if (i == 0) db->setToggleState (true, false); 
}  

For each iteration of the loop, the code creates a new Path component (a type of graphics path) and adds a star shape to it. The third parameter to addStar controls the number of points giving us five, six, seven and eight-pointed stars. You can think of a Path as a collection of shape outlines, but to do anything useful, we need to render it.

Thus, the path is copied into normal, an instance of DrawablePath class which (in Win32 API terms) brings a pen and brush to the party. The setSolidFill and setOutline methods do what they say on the tin, defining the inner fill colour and the colour and thickness of the pen used to render the shape. In exactly the same way, we create another DrawablePath, over, which is identical except for a darker blue interior.

A DrawableButton gets created next, specifying a caption (notice the slick String class which, through the magic of operator overloading, gives us some nice syntactic candy) and telling the button that the image should be above the text label. The call to setImages allows up to three separate images to be assigned to the button: normal, mouse-over, and mouse-down. In this case, the mouse-down case hasn't been used. What's clever here is that setImages expects a set of Drawable arguments, Drawable being the ancestor class of DrawablePath. In the same way, you could provide instances of DrawableText, DrawableImage or DrawableComposite. The latter is a container class, itself a descendant of Drawable which contains an arbitrary number of other Drawable instances. This is a good illustration of the flexibility that Julian has built into the library.

Finally, the code adds the button to the current demo page, makes it visible, specifies that clicking the button toggles the state and – here's another clever bit – assigns all four buttons to the same radio group. This causes them to act in concert as a radio button group, even though we're not actually dealing with radio buttons per se.

Figure 2 shows that not only does Juce support the playback of Quicktime movies but it'll also play two at the same time. You obviously need to have Quicktime installed on a Windows system for this to work.

Screenshot showing Juce support for Quicktime movies.

MI makes good...

Part of the reason for Juce's flexibility lies in the elegant use of – dare I mention it – multiple inheritance (MI). I've never been a huge devotee of MI in the past. This was partly sour grapes (Delphi never implemented MI!) and partly because, in my experience, it can result in a minefield of confused and confusing code, similar to the carnage that arises from the unrestrained use of Delphi's WITH statement. However, in Juce, Julian seems to have got it about right. As an example, look at the "Paths and Transforms" page of the demo. You'll see that the source code for this part of the application kicks off with:

class PathsAndTransformsDemo  : public Component, 
                                public SliderListener, 
                                public ComboBoxListener 

{
  // More code here...

This particular part of the demo includes a combo-box component and a set of five sliders, so it needs to be able to listen out for events coming from those two types of user interface control – hence the above declaration. You can create a slider as easily as this:

addAndMakeVisible (scaleSlider    = new Slider (T("scale")));

…and add the Slider to the current component (here, the PathsAndTransformsDemo class) like this:

scaleSlider ->addListener (this);

Once done, the “listening” class can then override sliderValueChanged (defined inside SliderListener) like this:

void sliderValueChanged (Slider*)

{
    repaint();

}

The originating Slider instance is passed to the event handler so the listener can disambiguate multiple sliders on the same page. In this case, we don't need to do that – all five sliders simply trigger a repaint. Two other events, sliderDragStarted and sliderDragEnded, are also defined and can likewise be overridden in the listener, if required.

The point of mentioning all this? You'll remember that last month I looked at Qt which uses a sophisticated signal/slot mechanism in conjunction with a meta-object compiler to connect widgets to classes that respond to user interface events. Using multiple inheritance, Julian has come up with an entirely different – and arguably more straightforward – technique for connecting event producers to event consumers.

I'm sure there are deep, technical reasons why Trolltech went with the signal/slot mechanism, but from where I'm sitting, the only disadvantage of Julian's technique would seem to be that callbacks have a fixed function prototype. You can't, for example, pass arbitrary data (such as the current slider position) as an argument to the callback, thus necessitating a further interrogative call to the widget from within the event handler). To my mind, that's no big drawback. It's certainly interesting to compare the different approaches. As ever, there's always more than one way to skin a cat.

More Juce in use

In addition to the source code for the core library, the demo program and a simple little "Hello World" project (designed to get you started), you also get complete source to the Juicer. This is a visual tool that enables you to lay out components on a form, establish basic size/position for each child component, set up callback routines, and so on. You can test the interface within Juicer and, once you're happy, it will spit out a C++ source file with corresponding header which can then be incorporated directly into your project. The same .CPP file can be loaded back into Juicer, provided that you retain the XML-based metadata which Juicer places within the source. This is done by enclosing the metadata inside a "#if 0" block, thereby preventing the compiler from attempting to compile it! This is obviously a lot less work than attempting to reparse the C++ code. Juicer isn't complete (for example, not every callback for every widget type is implemented), but it eliminates the need for a lot of tedious hand-coding of complex control layouts.

Before I wind up, I should also mention another cunning little innovation in the shape of Julian's freely downloadable BinaryBuilder utility. These days, applications typically need a lot of additional binary data such as image icons, sound files, and so on. The means of encoding this information into an executable is obviously platform-dependent (bundles under OS X, custom resources on Windows, etc) which isn't a great thing from a cross-platform perspective. The BinaryBuilder utility neatly gets around this by scanning a designated directory and encoding every encountered file into an unsigned character array, each of which ends up as a field inside the target class. For example, the demo program has a directory called binarydata containing a ZIP file, a PNG file, a WAV, and some other stuff. These files all end up as arrays within the BinaryData.cpp file that's generated by BinaryBuilder.

You might think this is inefficient, but I did just mention ZIP files! Along with everything else, Juce has built-in ZIP file support. Decompressing a random SVG file from a ZIP stream created via BinaryBuilder is as easy as this:

MemoryInputStream iconsFileStream (BinaryData::icons_zip, BinaryData::icons_zipSize, false); 

ZipFile icons (&iconsFileStream, false); 

// Load a random SVG file from our embedded icons.zip file.

InputStream* svgFileStream = icons.createStreamForEntry 
(Random::getSystemRandom().nextInt(icons.getNumEntries()));

if (svgFileStream != 0)
{
  Drawable* loadedSVG = Drawable::createFromImageDataStream (*svgFileStream);
  // etc...

Summary

So is Julian operating a charity here? Well, no. If you use Juce to create GPL'd open-source applications, then you pay nothing. However, if you want to create commercial applications, you'll need to pay £399+VAT for a commercial license. This entitles you to use Juce in just one product. Additional licenses cost another £399 per product, or, more sensibly, you can pay an additional £300 to upgrade to a full commercial license for the creation of an unlimited number of commercial products. This obviously compares very favourably with Qt: where a three-platform (Windows, Mac, Linux) commercial license will cost you $6600 or over £3,300.

Bottom line? I'm very impressed with Juce. Not only is it reasonably priced, full-featured, and very well thought out, but it also produces tight executables making it ideally suited not only for larger projects but small shareware utilities. Even the supplied source code is a joy to browse as Julian has used a consistent, intuitive naming style for all his classes and methods. You can download a compiled HTML (CHM) file of reference documentation from the Juce website, or browse online here.

And in Figure 3, here's Juicer running on Mac OS X with a screenshot of our "Fearless Leader" (see support forum!) How do you know it's running on the Mac? The subtle give away is the caption bar – the close/maximise/minimise buttons have moved over to the left: compare with other screenshots. ®

Shows Juicer running on Mac OS X.