Scaffold small client application frameworkwww.meldcraft.com
Slacker Guide to Java Swing Application Development
Using the Scaffold small client application framework

MarsViewer Application

One powerful feature of the Scaffold framework is extending existing applications facilitated by layered properties. The MarsViewer extends the ImageViewer example application to leverage existing infrastructure (image loading).

The MarsViewer is basically a clone of the Task Demo (SingleFrameExample5) featured in the JSR 296 - Swing Application Framework web site.

MarsViewer2 Screen Shot

Step 1 - Extend the ImageViewer

Recall the ImageViewer application which opens an image via a file chooser asynchronously, updating a progress bar and blocking input with a busy cursor while loading.

The MarsViewer is basically an ImageViewer, but differs in a few key ways:

  • Uses a hard coded list of locations, instead of letting the user select a file
  • Images are fetched from a remote server (via HTTP), instead of a local file
  • Image loading can be interrupted, either to just stop, or to subsequently download another image instead (e.g. the next or previous image in the list)

First, lets just make the GUI changes - we'll hook up the new functionality later. All that's needed is a new properties file, and a few icons for the new actions. The Scaffold layered properties feature allows us to reuse the ImageViewer application completely in tact, and just override the parts (i.e. properties) that differ.

Application Files
com/meldcraft/marsviewer/MarsViewer.properties
com/meldcraft/marsviewer/aiNext.png
com/meldcraft/marsviewer/aiPrev.png
com/meldcraft/marsviewer/aiReload.png
com/meldcraft/marsviewer/aiStop.png
com/meldcraft/marsviewer/MarsViewer.properties
baseResourceLayers = com.meldcraft.imageviewer.ImageViewer

Application.name = Huge Mars Rover Images From JPL

Application.mainmenu = File View History Help
Application.maintoolbar = Prev Next | Reload | Stop

File.items = Exit
View.items = Stop Reload
History.items = Prev Next

Nav.{1}.name = {2}
Nav.{1}.accelerator = {3}
Nav.replicateElement = Prev;_Back;control LEFT | control NP_LEFT,\
                       Next;_Forward;control RIGHT | control NP_RIGHT,\
                       Reload;_Reload;control R

Stop.name = _Stop
Stop.accelerator = control S

Nav2.{1}.elementProperty.hideActionText = false
Nav2.replicateElement = Prev, Next, Reload, Stop

Compile and run the application:

java -cp Scaffold.jar;ScaffoldGUIS.jar;ImageViewer.jar;MarsViewer/classes \
com.meldcraft.application.AppManager \
com.meldcraft.marsviewer.MarsViewerApplication

MarsViewer1 Screen Shot

So, the menu and toolbar items have been updated, and the image area and status bar remain in tact - just what we wanted.

Note that the ImageViewer.jar from the ImageViewer application is used (unmodified) in the classpath. The baseResourceLayers property in MarsViewer.properties tells Scaffold to load the ImageViewer properties first, and then add to and override those properties with values in the MarsViewer.properties file (just one of several ways to layer properties, by the way).

Step 2 - Hook Up Image List

Now, we can add the list of images, and hook up the navigation buttons. We'll create a new POJO to manage the list - basically a circular queue that can give us the next, previous and current image location.

Application Files
com/meldcraft/marsviewer/List.java
com/meldcraft/marsviewer/MarsViewer.properties
com/meldcraft/marsviewer/aiNext.png
com/meldcraft/marsviewer/aiPrev.png
com/meldcraft/marsviewer/aiReload.png
com/meldcraft/marsviewer/aiStop.png
com/meldcraft/marsviewer/List.java
package com.meldcraft.marsviewer;

public class List
{
    private String[]    mItems;
    private int         mIndex = 0;

    public void setItems(String[] items)
    {
        mItems = items;
    }

    public synchronized String next()
    {
        String ret = null;

        String[] items = mItems;
        if ((items != null) && (items.length > 0))
        {
            mIndex = (mIndex + 1) % items.length;
            ret = items[mIndex];
        }

        return ret;
    }

    public synchronized String previous()
    {
        String ret = null;

        String[] items = mItems;
        if ((items != null) && (items.length > 0))
        {
            if (mIndex > items.length)
            {
                mIndex = items.length;
            }

            if (mIndex == 0)
            {
                mIndex = items.length - 1;
            }
            else
            {
                mIndex--;
            }

            ret = items[mIndex];
        }

        return ret;
    }

    public synchronized String current()
    {
        String ret = null;

        String[] items = mItems;
        if ((items != null) && (items.length > 0))
        {
            int idx = (mIndex >= items.length) ? items.length-1 : mIndex;
            ret = items[idx];
        }

        return ret;
    }
}
com/meldcraft/marsviewer/MarsViewer.properties
baseResourceLayers = com.meldcraft.imageviewer.ImageViewer
size = 600,500

Application.name = Huge Mars Rover Images From JPL

Application.mainmenu = File View History Help
Application.maintoolbar = Prev Next | Reload | Stop

File.items = Exit
View.items = Stop Reload
History.items = Prev Next

Nav.{1}.name = {2}
Nav.{1}.accelerator = {3}
Nav.{1}.target = List
Nav.{1}.method = {4}
Nav.replicateElement = Prev;_Back;control LEFT | control NP_LEFT;previous(),\
                       Next;_Forward;control RIGHT | control NP_RIGHT;next(),\
                       Reload;_Reload;control R;current()

Stop.name = _Stop
Stop.accelerator = control S
Stop.ContextCheckingAction.key = LoadingImage

Nav2.{1}.elementProperty.hideActionText = false
Nav2.replicateElement = Prev, Next, Reload, Stop

List.className = com.meldcraft.marsviewer.List

Items.replicateElement = PIA03171, PIA02652, PIA05108, PIA02696,\
                         PIA05049, PIA05460, PIA07327, PIA05117,\
                         PIA05199, PIA05990, PIA03623
Items.replicateList.List.elementProperty.items = \
                                    http://photojournal.jpl.nasa.gov/jpeg/{1}.jpg

ImageLoader.elementProperty.addIIOReadProgressListener = \
                                    <proxy@:onThreadInterrupt=<target>[0].abort@>

Application.contextListeners+= ,Next.invokeReturn=NavInvoker,\
                               Prev.invokeReturn=NavInvoker,\
                               Reload.invokeReturn=NavInvoker,\
                               Stop.action=StopInvoker,\
                               Application.initialized=InitInvoker,\
                               FileOpenInvoker.invokeStop=StoppedInvoker,\
                               FileOpenInvoker.invokeStart+=,LoadingInvoker,\
                               FileOpenInvoker.invokeEnd+=,NotLoadingInvoker

InitInvoker.forward = Reload.action=<null>

NavInvoker.forward = FileOpenInvoker.jobAction=stop,\
                     Open.invokeReturn=<target>

FileOpenInvoker.busyCursor = false
FileOpenInvoker.invokeTargetThread = pool
FileOpenInvoker.invokeTargetThread.poolCoalesce = true
FileOpenInvoker.jobActionable = true

TitleInvoker.invokeCondition = <target>.completed
ClearIconInvoker.invokeCondition = <null>

LoadingInvoker.forward = LoadingImage=true
NotLoadingInvoker.forward = LoadingImage=false

StopInvoker.forward = FileOpenInvoker.jobAction=stop

StoppedInvoker.target = StatusBar.Label
StoppedInvoker.method = setText
StoppedInvoker.method.arg = Stopped.

Compile and run the application:

java -cp Scaffold.jar;ScaffoldGUIS.jar;ImageViewer.jar;MarsViewer/classes \
com.meldcraft.application.AppManager \
com.meldcraft.marsviewer.MarsViewerApplication

MarsViewer2 Progress Screen Shot

MarsViewer2 Screen Shot

The first image is loaded when the application starts - displays loading progress just like the ImageViewer.

Notes:

  1. Unlike the ImageViewer, there is no busy cursor - the user can press Next, Previous, Reload or Stop at any time to interrupt the current download
  2. The Stop button is enabled only while an image is loading. This is configured by the ContextCheckingAction.key property, in coordinatation with the LoadingInvoker and NotLoadingInvoker - the default Scaffold action offers this means of setting the action enabled state via the ApplicationContext messaging.
  3. Pressing the Stop button will stop the current download as soon as a method on the IIOReadProgressListener proxy is invoked.
  4. Pressing Next, Previous or Reload will stop the current download (as soon as a method on the IIOReadProgressListener proxy is invoked), and then immediately start the new download.
  5. The += operator is used to append to the preexisting ImageViewer properties for Application.contextListeners, FileOpenInvoker.invokeStart, and FileOpenInvoker.invokeEnd.

Below is a summary of the console log during application startup that shows the ApplicationContext messaging:

... (application initialization)
2007/08/31 13:44:08 PDT INFO: SwingApplicationContext [Huge Mars Rover Images From JPL] contextChanged[main]: key=Application.initialized value=null
2007/08/31 13:44:08 PDT INFO: SwingApplicationContext [Huge Mars Rover Images From JPL] contextChanged[main]: key=Reload.action value=null
2007/08/31 13:44:08 PDT INFO: SwingApplicationContext [Huge Mars Rover Images From JPL] contextChanged[main]: key=List value=com.meldcraft.marsviewer.List@1174b07
2007/08/31 13:44:08 PDT INFO: SwingApplicationContext [Huge Mars Rover Images From JPL] contextChanged[main]: key=Reload.invokeStart value=0,false,null
2007/08/31 13:44:08 PDT INFO: SwingApplicationContext [Huge Mars Rover Images From JPL] contextChanged[main]: key=Reload.invokeReturn value=http://photojournal.jpl.nasa.gov/jpeg/PIA03171.jpg
2007/08/31 13:44:08 PDT INFO: SwingApplicationContext [Huge Mars Rover Images From JPL] contextChanged[main]: key=FileOpenInvoker.jobAction value=stop
2007/08/31 13:44:08 PDT INFO: SwingApplicationContext [Huge Mars Rover Images From JPL] contextChanged[main]: key=Open.invokeReturn value=http://photojournal.jpl.nasa.gov/jpeg/PIA03171.jpg
2007/08/31 13:44:08 PDT INFO: SwingApplicationContext [Huge Mars Rover Images From JPL] contextChanged[main]: key=ImageLoader value=com.meldcraft.imageviewer.ImageLoader@6b7920
2007/08/31 13:44:08 PDT INFO: SwingApplicationContext [Huge Mars Rover Images From JPL] contextChanged[main]: key=Reload.invokeEnd value=0,true,null
2007/08/31 13:44:08 PDT INFO: SwingApplicationContext [Huge Mars Rover Images From JPL] contextChanged[ReflectInvoker-FileOpenInvoker-Thread-0]: key=FileOpenInvoker.invokeStart value=1,false,http://photojournal.jpl.nasa.gov/jpeg/PIA03171.jpg
2007/08/31 13:44:08 PDT INFO: SwingApplicationContextChangedInvoker [Huge Mars Rover Images From JPL] contextChanged[ReflectInvoker-FileOpenInvoker-Thread-0]: OpeningInvoker using EDT thread override for javax.swing.JLabel[,2,5,438x16,alignmentX=0.0,alignmentY=0.0,border=,flags=8388608,maximumSize=,minimumSize=,preferredSize=,defaultIcon=,disabledIcon=,horizontalAlignment=LEADING,horizontalTextPosition=TRAILING,iconTextGap=4,labelFor=,text=,verticalAlignment=CENTER,verticalTextPosition=CENTER]
2007/08/31 13:44:08 PDT INFO: SwingApplicationContext [Huge Mars Rover Images From JPL] contextChanged[AWT-EventQueue-0]: key=OpeningInvoker.invokeStart value=2,false,1,false,http://photojournal.jpl.nasa.gov/jpeg/PIA03171.jpg
2007/08/31 13:44:08 PDT INFO: SwingApplicationContext [Huge Mars Rover Images From JPL] contextChanged[AWT-EventQueue-0]: key=OpeningInvoker.invokeReturn value=null
2007/08/31 13:44:08 PDT INFO: SwingApplicationContext [Huge Mars Rover Images From JPL] contextChanged[AWT-EventQueue-0]: key=OpeningInvoker.invokeEnd value=2,true,1,false,http://photojournal.jpl.nasa.gov/jpeg/PIA03171.jpg
2007/08/31 13:44:08 PDT INFO: SwingApplicationContext [Huge Mars Rover Images From JPL] contextChanged[ReflectInvoker-FileOpenInvoker-Thread-0]: key=LoadingImage value=true
2007/08/31 13:44:09 PDT INFO: SwingApplicationContext [Huge Mars Rover Images From JPL] contextChanged[ReflectInvoker-FileOpenInvoker-Thread-0]: key=ImageLoader.addIIOReadProgressListenerProxy.imageStarted value=[Ljava.lang.Object;@1edc073
2007/08/31 13:44:09 PDT INFO: SwingApplicationContext [Huge Mars Rover Images From JPL] contextChanged[ReflectInvoker-FileOpenInvoker-Thread-0]: key=ImageLoader.addIIOReadProgressListenerProxy.imageProgress value=[Ljava.lang.Object;@121f1d
2007/08/31 13:44:09 PDT INFO: SwingApplicationContextChangedInvoker [Huge Mars Rover Images From JPL] contextChanged[ReflectInvoker-FileOpenInvoker-Thread-0]: ProgressInvoker using EDT thread override for javax.swing.JProgressBar[,440,5,150x16,alignmentX=0.0,alignmentY=0.0,border=javax.swing.plaf.BorderUIResource$CompoundBorderUIResource@116471f,flags=8,maximumSize=,minimumSize=,preferredSize=,orientation=HORIZONTAL,paintBorder=true,paintString=false,progressString=,indeterminateString=false]
2007/08/31 13:44:09 PDT INFO: SwingApplicationContext [Huge Mars Rover Images From JPL] contextChanged[AWT-EventQueue-0]: key=ProgressInvoker.invokeStart value=3,false,[Ljava.lang.Object;@121f1d
2007/08/31 13:44:09 PDT INFO: SwingApplicationContext [Huge Mars Rover Images From JPL] contextChanged[AWT-EventQueue-0]: key=ProgressInvoker.invokeReturn value=null
2007/08/31 13:44:09 PDT INFO: SwingApplicationContext [Huge Mars Rover Images From JPL] contextChanged[AWT-EventQueue-0]: key=ProgressInvoker.invokeEnd value=3,true,[Ljava.lang.Object;@121f1d
2007/08/31 13:44:09 PDT INFO: SwingApplicationContext [Huge Mars Rover Images From JPL] contextChanged[ReflectInvoker-FileOpenInvoker-Thread-0]: key=ImageLoader.addIIOReadProgressListenerProxy.imageProgress value=[Ljava.lang.Object;@64f6cd
... (more progress)
2007/08/31 13:44:09 PDT INFO: SwingApplicationContextChangedInvoker [Huge Mars Rover Images From JPL] contextChanged[ReflectInvoker-FileOpenInvoker-Thread-0]: ProgressInvoker using EDT thread override for javax.swing.JProgressBar[,440,5,150x16,alignmentX=0.0,alignmentY=0.0,border=javax.swing.plaf.BorderUIResource$CompoundBorderUIResource@116471f,flags=8,maximumSize=,minimumSize=,preferredSize=,orientation=HORIZONTAL,paintBorder=true,paintString=false,progressString=,indeterminateString=false]
2007/08/31 13:44:09 PDT INFO: SwingApplicationContext [Huge Mars Rover Images From JPL] contextChanged[AWT-EventQueue-0]: key=ProgressInvoker.invokeStart value=4,false,[Ljava.lang.Object;@c832d2
2007/08/31 13:44:09 PDT INFO: SwingApplicationContext [Huge Mars Rover Images From JPL] contextChanged[AWT-EventQueue-0]: key=ProgressInvoker.invokeReturn value=null
2007/08/31 13:44:09 PDT INFO: SwingApplicationContext [Huge Mars Rover Images From JPL] contextChanged[AWT-EventQueue-0]: key=ProgressInvoker.invokeEnd value=4,true,[Ljava.lang.Object;@c832d2
2007/08/31 13:44:09 PDT INFO: SwingApplicationContext [Huge Mars Rover Images From JPL] contextChanged[ReflectInvoker-FileOpenInvoker-Thread-0]: key=ImageLoader.addIIOReadProgressListenerProxy.imageProgress value=[Ljava.lang.Object;@166a22b
... (more progress)
2007/08/31 13:44:09 PDT INFO: SwingApplicationContext [Huge Mars Rover Images From JPL] contextChanged[ReflectInvoker-FileOpenInvoker-Thread-0]: key=FileOpenInvoker.invokeReturn value=BufferedImage@15b0afd: type = 5 ColorModel: #pixelBits = 24 numComponents = 3 color space = java.awt.color.ICC_ColorSpace@2e7820 transparency = 1 has alpha = false isAlphaPre = false ByteInterleavedRaster: width = 6144 height = 1536 #numDataElements 3 dataOff[0] = 2
2007/08/31 13:44:09 PDT INFO: SwingApplicationContextChangedInvoker [Huge Mars Rover Images From JPL] contextChanged[ReflectInvoker-FileOpenInvoker-Thread-0]: OpenIconInvoker using EDT thread override for javax.swing.JLabel[,0,0,588x367,alignmentX=0.0,alignmentY=0.0,border=,flags=8388608,maximumSize=,minimumSize=,preferredSize=,defaultIcon=,disabledIcon=,horizontalAlignment=CENTER,horizontalTextPosition=TRAILING,iconTextGap=4,labelFor=,text=,verticalAlignment=CENTER,verticalTextPosition=CENTER]
2007/08/31 13:44:09 PDT INFO: SwingApplicationContext [Huge Mars Rover Images From JPL] contextChanged[AWT-EventQueue-0]: key=OpenIconInvoker.invokeStart value=5,false,BufferedImage@15b0afd: type = 5 ColorModel: #pixelBits = 24 numComponents = 3 color space = java.awt.color.ICC_ColorSpace@2e7820 transparency = 1 has alpha = false isAlphaPre = false ByteInterleavedRaster: width = 6144 height = 1536 #numDataElements 3 dataOff[0] = 2
2007/08/31 13:44:09 PDT INFO: SwingApplicationContext [Huge Mars Rover Images From JPL] contextChanged[AWT-EventQueue-0]: key=OpenIconInvoker.invokeReturn value=null
2007/08/31 13:44:09 PDT INFO: SwingApplicationContext [Huge Mars Rover Images From JPL] contextChanged[AWT-EventQueue-0]: key=OpenIconInvoker.invokeEnd value=5,true,BufferedImage@15b0afd: type = 5 ColorModel: #pixelBits = 24 numComponents = 3 color space = java.awt.color.ICC_ColorSpace@2e7820 transparency = 1 has alpha = false isAlphaPre = false ByteInterleavedRaster: width = 6144 height = 1536 #numDataElements 3 dataOff[0] = 2
2007/08/31 13:44:09 PDT INFO: SwingApplicationContextChangedInvoker [Huge Mars Rover Images From JPL] contextChanged[ReflectInvoker-FileOpenInvoker-Thread-0]: ClearErrorInvoker using EDT thread override for javax.swing.JLabel[,2,5,438x16,alignmentX=0.0,alignmentY=0.0,border=,flags=8388608,maximumSize=,minimumSize=,preferredSize=,defaultIcon=,disabledIcon=,horizontalAlignment=LEADING,horizontalTextPosition=TRAILING,iconTextGap=4,labelFor=,text=Opening http://photojournal.jpl.nasa.gov/jpeg/PIA03171.jpg,verticalAlignment=CENTER,verticalTextPosition=CENTER]
2007/08/31 13:44:09 PDT INFO: SwingApplicationContext [Huge Mars Rover Images From JPL] contextChanged[AWT-EventQueue-0]: key=ClearErrorInvoker.invokeStart value=6,false,BufferedImage@15b0afd: type = 5 ColorModel: #pixelBits = 24 numComponents = 3 color space = java.awt.color.ICC_ColorSpace@2e7820 transparency = 1 has alpha = false isAlphaPre = false ByteInterleavedRaster: width = 6144 height = 1536 #numDataElements 3 dataOff[0] = 2
2007/08/31 13:44:09 PDT INFO: SwingApplicationContext [Huge Mars Rover Images From JPL] contextChanged[AWT-EventQueue-0]: key=ClearErrorInvoker.invokeReturn value=null
2007/08/31 13:44:09 PDT INFO: SwingApplicationContext [Huge Mars Rover Images From JPL] contextChanged[AWT-EventQueue-0]: key=ClearErrorInvoker.invokeEnd value=6,true,BufferedImage@15b0afd: type = 5 ColorModel: #pixelBits = 24 numComponents = 3 color space = java.awt.color.ICC_ColorSpace@2e7820 transparency = 1 has alpha = false isAlphaPre = false ByteInterleavedRaster: width = 6144 height = 1536 #numDataElements 3 dataOff[0] = 2
2007/08/31 13:44:09 PDT INFO: SwingApplicationContext [Huge Mars Rover Images From JPL] contextChanged[ReflectInvoker-FileOpenInvoker-Thread-0]: key=FileOpenInvoker.invokeEnd value=1,true,http://photojournal.jpl.nasa.gov/jpeg/PIA03171.jpg
2007/08/31 13:44:09 PDT INFO: SwingApplicationContextChangedInvoker [Huge Mars Rover Images From JPL] contextChanged[ReflectInvoker-FileOpenInvoker-Thread-0]: TitleInvoker using EDT thread override for javax.swing.JFrame[frame0,500,350,600x500,invalid,layout=java.awt.BorderLayout,title=Huge Mars Rover Images From JPL,resizable,normal,defaultCloseOperation=DO_NOTHING_ON_CLOSE,rootPane=javax.swing.JRootPane[,4,23,592x473,layout=javax.swing.JRootPane$RootLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=16777673,maximumSize=,minimumSize=,preferredSize=],rootPaneCheckingEnabled=true]
2007/08/31 13:44:09 PDT INFO: SwingApplicationContext [Huge Mars Rover Images From JPL] contextChanged[AWT-EventQueue-0]: key=TitleInvoker.invokeStart value=7,false,1,true,http://photojournal.jpl.nasa.gov/jpeg/PIA03171.jpg
2007/08/31 13:44:09 PDT INFO: SwingApplicationContext [Huge Mars Rover Images From JPL] contextChanged[AWT-EventQueue-0]: key=TitleInvoker.invokeReturn value=null
2007/08/31 13:44:09 PDT INFO: SwingApplicationContext [Huge Mars Rover Images From JPL] contextChanged[AWT-EventQueue-0]: key=TitleInvoker.invokeEnd value=7,true,1,true,http://photojournal.jpl.nasa.gov/jpeg/PIA03171.jpg
2007/08/31 13:44:09 PDT INFO: SwingApplicationContextChangedInvoker [Huge Mars Rover Images From JPL] contextChanged[ReflectInvoker-FileOpenInvoker-Thread-0]: ProgressCompleteInvoker using EDT thread override for javax.swing.JProgressBar[,440,5,150x16,alignmentX=0.0,alignmentY=0.0,border=javax.swing.plaf.BorderUIResource$CompoundBorderUIResource@116471f,flags=8,maximumSize=,minimumSize=,preferredSize=,orientation=HORIZONTAL,paintBorder=true,paintString=false,progressString=,indeterminateString=false]
2007/08/31 13:44:09 PDT INFO: SwingApplicationContext [Huge Mars Rover Images From JPL] contextChanged[AWT-EventQueue-0]: key=ProgressCompleteInvoker.invokeStart value=8,false,1,true,http://photojournal.jpl.nasa.gov/jpeg/PIA03171.jpg
2007/08/31 13:44:09 PDT INFO: SwingApplicationContext [Huge Mars Rover Images From JPL] contextChanged[AWT-EventQueue-0]: key=ProgressCompleteInvoker.invokeReturn value=null
2007/08/31 13:44:09 PDT INFO: SwingApplicationContext [Huge Mars Rover Images From JPL] contextChanged[AWT-EventQueue-0]: key=ProgressCompleteInvoker.invokeEnd value=8,true,1,true,http://photojournal.jpl.nasa.gov/jpeg/PIA03171.jpg
2007/08/31 13:44:09 PDT INFO: SwingApplicationContext [Huge Mars Rover Images From JPL] contextChanged[ReflectInvoker-FileOpenInvoker-Thread-0]: key=LoadingImage value=false
© 2007 Meldcraft Corporation