//
// PreviewPane.java
//
/*
OME Bio-Formats package for reading and converting biological file formats.
Copyright (C) 2005-@year@ UW-Madison LOCI and Glencoe Software, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package loci.formats.gui;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import loci.formats.FormatException;
import loci.formats.FormatTools;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* PreviewPane is a panel for use as a JFileChooser accessory, displaying
* a thumbnail for the selected image, loaded in a separate thread.
*
* <dl><dt><b>Source code:</b></dt>
* <dd><a href="http://trac.openmicroscopy.org.uk/ome/browser/bioformats.git/components/bio-formats/src/loci/formats/gui/PreviewPane.java">Trac</a>,
* <a href="http://git.openmicroscopy.org/?p=bioformats.git;a=blob;f=components/bio-formats/src/loci/formats/gui/PreviewPane.java;hb=HEAD">Gitweb</a></dd></dl>
*/
public class PreviewPane extends JPanel
implements PropertyChangeListener, Runnable
{
// -- Constants --
private static final Logger LOGGER =
LoggerFactory.getLogger(PreviewPane.class);
// -- Fields --
/** Reader for use when loading thumbnails. */
protected BufferedImageReader reader;
/** Current ID to load. */
protected String loadId;
/** Last ID loaded. */
protected String lastId;
/** Thumbnail loading thread. */
protected Thread loader;
/** Flag indicating whether loader thread should keep running. */
protected boolean loaderAlive;
/** Method for syncing the view to the model. */
protected Runnable refresher;
// -- Fields - view --
/** Labels containing thumbnail and dimensional information. */
protected JLabel iconLabel, formatLabel, resLabel, zctLabel, typeLabel;
// -- Fields - model --
protected ImageIcon icon;
protected String iconText, formatText, resText, zctText, typeText;
protected String iconTip, formatTip, resTip, zctTip, typeTip;
// -- Constructor --
/** Constructs a preview pane for the given file chooser. */
public PreviewPane(JFileChooser jc) {
super();
reader = new BufferedImageReader();
reader.setNormalized(true);
// create view
setBorder(new EmptyBorder(0, 10, 0, 10));
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
iconLabel = new JLabel();
iconLabel.setMinimumSize(new java.awt.Dimension(128, -1));
iconLabel.setAlignmentX(JLabel.CENTER_ALIGNMENT);
add(iconLabel);
add(Box.createVerticalStrut(7));
formatLabel = new JLabel();
formatLabel.setAlignmentX(JLabel.CENTER_ALIGNMENT);
add(formatLabel);
add(Box.createVerticalStrut(5));
resLabel = new JLabel();
resLabel.setAlignmentX(JLabel.CENTER_ALIGNMENT);
add(resLabel);
zctLabel = new JLabel();
zctLabel.setAlignmentX(JLabel.CENTER_ALIGNMENT);
add(zctLabel);
typeLabel = new JLabel();
typeLabel.setAlignmentX(JLabel.CENTER_ALIGNMENT);
add(typeLabel);
// smaller font for most labels
Font font = formatLabel.getFont();
font = font.deriveFont(font.getSize2D() - 3);
formatLabel.setFont(font);
resLabel.setFont(font);
zctLabel.setFont(font);
typeLabel.setFont(font);
// populate model
icon = null;
iconText = formatText = resText = zctText = typeText = "";
iconTip = formatTip = resTip = zctTip = typeTip = null;
if (jc != null) {
jc.setAccessory(this);
jc.addPropertyChangeListener(this);
refresher = new Runnable() {
public void run() {
iconLabel.setIcon(icon);
iconLabel.setText(iconText);
iconLabel.setToolTipText(iconTip);
formatLabel.setText(formatText);
formatLabel.setToolTipText(formatTip);
resLabel.setText(resText);
resLabel.setToolTipText(resTip);
zctLabel.setText(zctText);
zctLabel.setToolTipText(zctTip);
typeLabel.setText(typeText);
typeLabel.setToolTipText(typeTip);
}
};
// start separate loader thread
loaderAlive = true;
loader = new Thread(this, "Preview");
loader.start();
}
}
// -- Component API methods --
/* @see java.awt.Component#getPreferredSize() */
public Dimension getPreferredSize() {
Dimension prefSize = super.getPreferredSize();
return new Dimension(148, prefSize.height);
}
// -- PropertyChangeListener API methods --
/**
* Property change event, to listen for when a new
* file is selected, or the file chooser closes.
*/
public void propertyChange(PropertyChangeEvent e) {
String prop = e.getPropertyName();
if (prop.equals("JFileChooserDialogIsClosingProperty")) {
// notify loader thread that it should stop
loaderAlive = false;
}
if (!prop.equals(JFileChooser.SELECTED_FILE_CHANGED_PROPERTY)) return;
File f = (File) e.getNewValue();
if (f != null && (f.isDirectory() || !f.exists())) f = null;
loadId = f == null ? null : f.getAbsolutePath();
}
// -- Runnable API methods --
/** Thumbnail loading routine. */
public void run() {
while (loaderAlive) {
try { Thread.sleep(100); }
catch (InterruptedException exc) { LOGGER.info("", exc); }
try { // catch-all for unanticipated exceptions
final String id = loadId;
if (id == lastId) continue;
if (id != null && lastId != null) {
String[] files = reader.getUsedFiles();
boolean found = false;
for (int i=0; i<files.length; i++) {
if (id.equals(files[i])) {
found = true;
lastId = id;
break;
}
}
if (found) continue;
}
lastId = id;
icon = null;
iconText = id == null ? "" : "Reading...";
formatText = resText = zctText = typeText = "";
iconTip = id;
formatTip = resTip = zctTip = typeTip = "";
if (id == null) {
SwingUtilities.invokeLater(refresher);
continue;
}
try { reader.setId(id); }
catch (FormatException exc) {
LOGGER.debug("Failed to initialize {}", id, exc);
boolean badFormat =
exc.getMessage().startsWith("Unknown file format");
iconText = "Unsupported " + (badFormat ? "format" : "file");
formatText = resText = "";
SwingUtilities.invokeLater(refresher);
lastId = null;
continue;
}
catch (IOException exc) {
LOGGER.debug("Failed to initialize {}", id, exc);
iconText = "Unsupported file";
formatText = resText = "";
SwingUtilities.invokeLater(refresher);
lastId = null;
continue;
}
if (id != loadId) {
SwingUtilities.invokeLater(refresher);
continue;
}
icon = id == null ? null : new ImageIcon(makeImage("Loading..."));
iconText = "";
String format = reader.getFormat();
formatText = format;
formatTip = format;
resText = reader.getSizeX() + " x " + reader.getSizeY();
zctText = reader.getSizeZ() + "Z x " +
reader.getSizeT() + "T x " + reader.getSizeC() + "C";
typeText = reader.getRGBChannelCount() + " x " +
FormatTools.getPixelTypeString(reader.getPixelType());
SwingUtilities.invokeLater(refresher);
// open middle image thumbnail
int z = reader.getSizeZ() / 2;
int t = reader.getSizeT() / 2;
int ndx = reader.getIndex(z, 0, t);
BufferedImage thumb = null;
try { thumb = reader.openThumbImage(ndx); }
catch (FormatException exc) {
LOGGER.debug("Failed to read thumbnail #{} from {}",
new Object[] {ndx, id}, exc);
}
catch (IOException exc) {
LOGGER.debug("Failed to read thumbnail #{} from {}",
new Object[] {ndx, id}, exc);
}
icon = new ImageIcon(thumb == null ? makeImage("Failed") : thumb);
iconText = "";
SwingUtilities.invokeLater(refresher);
}
catch (Exception exc) {
LOGGER.info("", exc);
icon = null;
iconText = "Thumbnail failure";
formatText = resText = zctText = typeText = "";
iconTip = loadId;
formatTip = resTip = zctTip = typeTip = "";
SwingUtilities.invokeLater(refresher);
}
}
}
// -- PreviewPane API methods --
/** Closes the underlying image reader. */
public void close() throws IOException {
if (reader != null) {
reader.close();
}
}
// -- Helper methods --
/**
* Creates a blank image with the given message painted on top (e.g.,
* a loading or error message), matching the size of the active reader's
* thumbnails.
*/
private BufferedImage makeImage(String message) {
int w = reader.getThumbSizeX(), h = reader.getThumbSizeY();
if (w < 128) w = 128;
if (h < 32) h = 32;
BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
Graphics2D g = image.createGraphics();
Rectangle2D.Float r = (Rectangle2D.Float)
g.getFont().getStringBounds(message, g.getFontRenderContext());
g.drawString(message, (w - r.width) / 2, (h - r.height) / 2 + r.height);
g.dispose();
return image;
}
}