/*
* Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. Use is
* subject to license terms.
*/
package examples;
import java.awt.image.BufferedImage;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JToolBar;
import org.mypsycho.swing.app.Action;
import org.mypsycho.swing.app.Application;
import org.mypsycho.swing.app.ApplicationListener;
import org.mypsycho.swing.app.SingleFrameApplication;
import org.mypsycho.swing.app.beans.StatusBar;
import org.mypsycho.swing.app.utils.SwingHelper;
import org.mypsycho.text.BeanTextMap;
import org.mypsycho.text.TextMap;
/**
* A demo of the Task class.
* <p>
* This demo highlights the importance of background tasks by
* downloading some very large Mars rover images from JPL's
* photojournal web site. There are about a dozen images, most with
* 10-15M pixels. Clicking the next/prev buttons (or control-N,P)
* cancels the current download and starts loading a new image. The
* stop button also cancels the current download. The list of images
* is defined in the startup() method. The first image is shown by
* the application's ready() method.
* <p>
* More images of Mars can be found here:
* <a href="http://photojournal.jpl.nasa.gov/target/Mars">
* http://photojournal.jpl.nasa.gov/target/Mars</a>. Some of the
* MER images are quite large (like this 22348x4487 whopper,
* http://photojournal.jpl.nasa.gov/jpeg/PIA06917.jpg) and can't
* be loaded without reconfiguring the Java heap parameters.
*
* @author Hans Muller (Hans.Muller@Sun.COM)
*/
public class SingleFrameExample5 extends SingleFrameApplication {
private JLabel imageLabel;
/* The following fields define the application's internal state.
* We track our current - imageIndex - position in the list of URLs
* (using a ListIterator instead seemed like a good idea but was not).
* The value of imageTask is managed by ShowImagTask, it's initialized
* (on the EDT) when the task is constructed and cleared (on the EDT)
* by the task, when it's done. The boolean @Action *enabled fields
* are updated by calling updateNextPreviousEnabledProperties().
*/
private List<URL> imageLocations;
private int imageIndex = 0;
private ShowImageTask imageTask = null;
private boolean nextImageEnabled = true;
private boolean previousImageEnabled = false;
private TextMap texts = new BeanTextMap(this);
/* A application specific subclass of LoadImageTask.
*
* This class is constructed on the EDT. The constructor
* stops the current ShowImageTask, if one is still
* running, clears the display (imageLabel) so that
* we'll only have one enormous image on the heap, and
* updates the enabled state of the next/previous @Actions.
* When the task completes, we update the GUI.
*/
private class ShowImageTask extends LoadImageTask {
ShowImageTask(URL imageURL) {
super(imageURL);
stopLoading();
imageTask = this;
showImageLoading(imageURL);
}
@Override protected void cancelled() {
if (imageTask == this) {
showImageCancelled(getImageURL());
}
}
@Override protected void succeeded(BufferedImage image) {
super.succeeded(image);
if (imageTask == this) {
showImage(getImageURL(), image);
}
}
@Override protected void failed(Throwable e) {
super.failed(e);
if (imageTask == this) {
showImageFailed(getImageURL());
}
}
@Override protected void finished() {
super.finished();
imageTask = null;
}
}
/* The next,previous,refreshImage actions clear the displayed
* image, by calling showImageLoading(), to free up heap space.
* Most of the images we're loading are so large that there's not
* enough heap space (by default) to accomodate both the old and
* new ones. We could adjust the heap size parameters to
* eliminate the problem, however it's more neighborly to just
* limit the heap's growth.
*/
@Action(enabled = "nextImageEnabled")
public ShowImageTask nextImage() {
ShowImageTask task = null;
if (imageIndex < (imageLocations.size() - 1)) {
imageIndex += 1;
updateNextPreviousEnabledProperties();
task = new ShowImageTask(imageLocations.get(imageIndex));
}
return task;
}
@Action(enabled = "previousImageEnabled")
public ShowImageTask previousImage() {
ShowImageTask task = null;
if (imageIndex > 0) {
imageIndex -= 1;
updateNextPreviousEnabledProperties();
task = new ShowImageTask(imageLocations.get(imageIndex));
}
return task;
}
public ShowImageTask refreshImage() {
return new ShowImageTask(imageLocations.get(imageIndex));
}
public void stopLoading() {
if ((imageTask != null) && !imageTask.isDone()) {
imageTask.cancel(true);
}
}
/* The properties below define the enabled state for the
* corresponding @Actions. The ApplicationActionMap
* class uses a PropertyChangeListener to keep the
* Actions in sync with their enabledProperty properties.
*/
private void updateNextPreviousEnabledProperties() {
setNextImageEnabled(imageIndex < (imageLocations.size() - 1));
setPreviousImageEnabled(imageIndex > 0);
}
public boolean isNextImageEnabled() {
return nextImageEnabled;
}
public void setNextImageEnabled(boolean nextImageEnabled) {
boolean oldValue = this.nextImageEnabled;
this.nextImageEnabled = nextImageEnabled;
firePropertyChange("nextImageEnabled", oldValue, this.nextImageEnabled);
}
public boolean isPreviousImageEnabled() {
return previousImageEnabled;
}
public void setPreviousImageEnabled(boolean previousImageEnabled) {
boolean oldValue = this.previousImageEnabled;
this.previousImageEnabled = previousImageEnabled;
firePropertyChange("previousImageEnabled", oldValue, this.previousImageEnabled);
}
/* The ShowImage Task calls one of the following showImage*
* methods. If the image is successfully loaded, then
* showImage() is called, otherwise showImageCancelled()
* or showImageFailed(). Before the ShowImage Task is
* executed the nextImage/previousImage @Actions call
* showImageLoading() to alert the user that the process
* has started, and to free up the heap-space occupied
* by the current image.
*/
private void showImage(URL imageURL, BufferedImage image) {
imageLabel.setToolTipText(getTexts().get("imageTooltip", imageURL, image));
imageLabel.setText(null);
imageLabel.setIcon(new ImageIcon(image));
}
private void showImageMessage(URL imageURL, String key) {
imageLabel.setToolTipText("");
imageLabel.setText(getTexts().get(key, imageURL));
imageLabel.setIcon(null);
}
private void showImageLoading(URL imageURL) {
showImageMessage(imageURL, "loadingWait");
}
private void showImageCancelled(URL imageURL) {
showImageMessage(imageURL, "loadingCancelled");
}
private void showImageFailed(URL imageURL) {
showImageMessage(imageURL, "loadingFailed");
}
@Override
protected void startup() {
getMainView().setToolBar(new JToolBar("toolbar"));
getMainView().setStatusBar(new StatusBar(this, getContext().getTaskMonitor()));
imageLabel = new JLabel();
show((JScrollPane) new SwingHelper("image", new JScrollPane(imageLabel)).get());
}
/**
* Runs after the startup has completed and the GUI is up and ready.
* We show the first image here, rather than initializing it at startup
* time, so loading the first image doesn't impede getting the
* GUI visible.
*/
protected void ready() {
getContext().getTaskService().execute(new ShowImageTask(imageLocations.get(0)));
}
public static void main(String[] args) {
Application app = new SingleFrameExample5();
app.addApplicationListener(ApplicationListener.console);
app.launch(args);
}
/**
* Returns the texts.
*
* @return the texts
*/
public TextMap getTexts() {
return texts;
}
/**
* Sets the imageLocations.
*
* @param imageLocations the imageLocations to set
*/
public void setImageLocations(URL... imageLocations) {
this.imageLocations = Arrays.asList(imageLocations);
}
}