Adapting an Existing Program
Well, that wasn't so bad, and once you do it the first time it becomes
pretty easy to manage it all with the Ant file. Now let's try something
more complicated: turning an existing application into a screensaver.
In a previous article,
"Getting Started with Scripting,"
I built a
simple particle simulator. It used a mixture of JavaScript and Java code to
create colorful displays on the screen. What better way to show off those
displays then to create a screensaver for them?
The SimEditor class is essentially a shell containing a SimView to
display the graphics, a JavascriptProvider to create the code buffers that
draw each frame, and a SimRunner to control the animation in a thread.
Turning this into a screensaver will be a simple exercise in refactoring.
To ensure our changes have as little impact on the codebase as possible,
I'm going to try to keep all of the new code in a single class,
SimScreenSaver. As with the Christmas counter, we need to make it a
SimpleSaver subclass. Since we won't have an onscreen editor to type in the
JavaScript, SimScreenSaver will have to be a JavascriptProvider, too. So
here's what we have so far:
public class SimScreenSaver
extends SimpleScreensaver
implements JavascriptProvider {
SimRunner runner;
SimFrame frame;
public String init, update, draw;
SimView view;
public void init() {
SimContext ctx = new SimContext();
ctx.setBlur(true);
ctx.setRadialStretch(true);
ctx.setMaxParticles(20);
load("circle.xml");
runner = new SimRunner();
runner.setContext(ctx);
context.setRunner(runner);
// setup the javascript provider frame and runner
frame = new JavascriptFrame(this);
runner.setFrame(frame);
runner.setup();
runner.initParticles();
// prepare the view
view = new SimView();
int w = getContext().getComponent().getWidth();
int h = getContext().getComponent().getHeight();
view.setSize(w,h);
view.setContext(ctx);
}
public String getInit() {
return init;
}
public String getUpdate() {
return update;
}
public String getDraw() {
return draw;
}
}
At the top there is a reference to each piece we will need: a SimRunner,
a SimFrame, and a SimView. In the init method, we create a SimContext to tie
each component together, and to hold the display settings (like the maximum
number of particles).
The load method (the implementation of which is not shown above) loads an XML file
containing each snippet of Java code into the init,
update, and draw code buffers. It loads the XML
from the classpath so the user doesn't have to worry about about where to
install the XML files.
At then end of the init method, you can see that we call getContext() and
then getComponent(). This is to get a reference to the actual drawing
canvas that fills the screen when the saver is activated. Using the size of
this canvas, we can set the size of the view. It is important not to hard
code the screen dimensions, because the end user's computer might be running
at a different resolution or the saver might be in preview mode (which is often
quite tiny).
After the init method is the implementation of
JavascriptProvider that returns the code buffers for the
init, update, and draw phases of the simulator.
Now that the screensaver is set up, we have to actually draw it. Normally,
we would call runner.start() to kick off the animation, but we
don't want to do that here. The SaverBeans framework has its own animation
thread that will call the SimScreenSaver's paint method at the
right time, so we don't need to use the SimRunner's threading. However, we
do want to reuse its loop method that launches the Java interpreter and
paints the screen. The only problem is that if we just called
runner.loop(), it would block forever. The method
loop is synchronized because it was originally intended for
use inside the runner's animation thread. To get around, this I have created
a second loop method called loopUnSynchronized() that does the same thing,
but without the synchronized keyword and the wait() call. This way, we can
use whichever form of looping is appropriate for the situation.
After the runner has looped, we just want to paint the view. The view is
a JPanel, but it hasn't been added to a JFrame and been realized on screen.
However, since we are running inside of the SaverBeans framework, we don't
need all of its Swing code. Instead, we just reuse its paintComponent()
method to draw the particles onscreen. Here is the new SimScreenSaver paint
method:
public void paint(Graphics g) {
runner.loopUnsynchronized();
view.paintComponent(g);
}
With this class, we can build a new screensaver like we did before and
drop the SimSaver.scr file into the system directory. The
running screensaver is shown in Figure 3.

Figure 3. SimScreenSaver running Circles
The simulator works. There's just one problem though. Using SimEditor,
the user could choose which sim to run and turn on the blurring, but these
values are hard coded in SimScreenSaver. It would be nice to provide a
control panel for the user to change these settings. Fortunately, the
SaverBeans framework provides a way to do this as well. You may recall that
the configuration XML is actually an XScreenSaver definition file. In addition to
holding startup parameters, the file can also define user-selectable
settings. The framework will automatically create a settings panel for the
user and then pass the selected settings to the screensaver. Under Windows,
this is done with a special Java application. Under Linux, the settings
screen is created by XScreenSaver itself.
Here is our new config file:
<?xml version="1.0" encoding="UTF-8"?>
<screensaver name="SimSaver"
_label="Javascript Particle Simulator Screensaver">
<command arg="-root"/>
...
<!-- start settings definitions -->
<select id="blur">
<option id="on" _label="Blur On"
arg-set="-blur on" />
<option id="off" _label="Blur Off"
arg-set="-blur off" />
</select>
<select id="radial">
<option id="on" _label="Radial Stretch On"
arg-set="-radial on" />
<option id="off" _label="Radial Stretch Off"
arg-set="-radial off" />
</select>
<select id="simfile">
<option id="circle.xml" _label="Simple Circle"
arg-set="-simfile circle.xml" />
<option id="swirl.xml" _label="Eccentric Circle"
arg-set="-simfile swirl.xml" />
<option id="falling.xml" _label="Falling Lines"
arg-set="-simfile falling.xml" />
<option id="volcano.xml" _label="Volcano"
arg-set="-simfile volcano.xml" />
<option id="raindrops.xml" _label="Rain Drops"
arg-set="-simfile raindrops.xml" />
</select>
</screensaver>
The config file above defines three select values: blur, radial, and
simfile, which will be displayed as three drop-down menus to the user. The
id of the select is the key we can use later to retrieve the
selected value. Each select has several options whose ids define the
possible values. The _label attribute is the text of the
option that will be displayed to the user. The arg-set defines
the options that will be passed to XScreenSaver under Linux, so those
should match the select and option ids. For example, the first select is
blur and its first option is on, so the
arg-set for the first option would be -blur on. With these
settings in the config file, we will get a settings panel, the same as when the
user selects settings in the screensaver control panel. The
settings panel is shown in Figure 4.

Figure 4. SimSaver settings screen
Back in our SimScreenSaver class, we need to retrieve the current
settings, providing defaults if the settings are missing (i.e., if the user
didn't select anything). The modified init() method looks like this:
public void init() {
ScreensaverSettings settings = getContext().getSettings();
SimContext ctx = new SimContext();
// setup blur
String blur = settings.getProperty("blur");
if(blur == null || blur.equals("on")) {
ctx.setBlur(true);
} else {
ctx.setBlur(false);
}
// setup radial stretch
String radial = settings.getProperty("radial");
if(radial == null || radial.equals("off")) {
ctx.setRadialStretch(false);
} else {
ctx.setRadialStretch(true);
}
ctx.setMaxParticles(20);
// setup and load the sim file
String file = settings.getProperty("simfile");
if(file == null) {
file = "circle.xml";
}
load(file);
... rest of init method
}
First we have to retrieve the ScreensaverSettings object
from the context with getContext().getSettings(). Next we test the value of
each property (checking for null, of course) and set the appropriate value
in the SimContext, ctx. With that, users can change the screensaver to
their hearts' content.
In addition to the sims from the original
scripting article,
I have included a new sim called Rain Drops. It draws ever-expanding circles that combine
with the blur effect to look like Figure 5:

Figure 5. Raindrops screensaver
Further Work
If you are interested in playing with the
simulator more, you can find the entire source on
my web site here.
The SaverBeans framework is here and you can find
tons of screensavers built by other developers. I recommend browsing the
screensavers forum to find tips and tricks on
building your own screensavers.
Conclusion
As part of the JDIC project, SaverBeans provides a framework for
building a type of Java application never before possible, and, as always,
making it cross-platform. In the few months since its release, JDIC has
proven that there is pent-up demand for doing interesting things with
desktop Java. As it matures, I hope JDIC will continue to grow and provide us
with the kind of robust feature set we expect from Java.
Resources
Joshua Marinacci first tried Java in 1995 at the request of his favorite TA and has never looked back.