//
// MetadataPane.java
//
/*
OME Metadata Editor application for exploration and editing of OME-XML and
OME-TIFF metadata. Copyright (C) 2006-@year@ Christopher Peterson.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Library 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 Library General Public License for more details.
You should have received a copy of the GNU Library 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.ome.editor;
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Frame;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Hashtable;
import java.util.Vector;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.JViewport;
import javax.swing.Scrollable;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import loci.common.DataTools;
import loci.common.RandomAccessInputStream;
import loci.common.RandomAccessOutputStream;
import loci.common.ReflectedUniverse;
import loci.common.services.ServiceFactory;
import loci.formats.FormatException;
import loci.formats.ImageReader;
import loci.formats.MetadataTools;
import loci.formats.gui.AWTImageTools;
import loci.formats.gui.BufferedImageReader;
import loci.formats.in.OMEXMLReader;
import loci.formats.in.TiffReader;
import loci.formats.meta.IMetadata;
import loci.formats.ome.OMEXMLMetadata;
import loci.formats.services.OMEXMLService;
import loci.formats.out.TiffWriter;
import loci.formats.tiff.IFD;
import loci.formats.tiff.TiffParser;
import loci.formats.tiff.TiffSaver;
import ome.xml.DOMUtil;
import ome.xml.OMEXMLNode;
import ome.xml.model.enums.DimensionOrder;
import ome.xml.model.enums.EnumerationException;
import ome.xml.model.primitives.PositiveInteger;
import org.openmicroscopy.xml.AttributeNode;
import org.openmicroscopy.xml.CustomAttributesNode;
import org.openmicroscopy.xml.OMENode;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import com.jgoodies.forms.builder.PanelBuilder;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;
/**
* A panel that displays OME-XML metadata.
* Most of the gui code is in here.
* If you want a panel instead of a window, instantiate this
* instead of MetadataEditor.
* Sadly you lose quite a bit of functionality such as the
* various views if you choose to directly instatiate.
*
* <dl><dt><b>Source code:</b></dt>
* <dd><a href="http://trac.openmicroscopy.org.uk/ome/browser/bioformats.git/components/legacy/ome-editor/src/loci/ome/editor/MetadataPane.java">Trac</a>,
* <a href="http://git.openmicroscopy.org/?p=bioformats.git;a=blob;f=components/legacy/ome-editor/src/loci/ome/editor/MetadataPane.java;hb=HEAD">Gitweb</a></dd></dl>
*
* @author Christopher Peterson crpeterson2 at wisc.edu
*/
public class MetadataPane extends JPanel
implements ActionListener, Runnable
{
// -- Constants --
/**Defines the names of columns in the TablePanels.*/
protected static final String[] TREE_COLUMNS = {"Attribute", "Value", "Goto"};
/**An icon that signifies metadata is present.*/
public static final ImageIcon DATA_BULLET =
createImageIcon("Icons/bullet-green.gif",
"An icon signifying that metadata is present.");
/**An icon that signifies no metadata is present.*/
public static final ImageIcon NO_DATA_BULLET =
createImageIcon("Icons/bullet-red.gif",
"An icon signifying that no metadata is present.");
/**The color that signifies a button's operation is to add something.*/
public static final Color ADD_COLOR =
new Color(0, 100, 0);
/**The color that signifies a button's operation is to delete something.*/
public static final Color DELETE_COLOR =
new Color(100, 0, 0);
/**The main text color of most things.*/
public static final Color TEXT_COLOR =
new Color(0, 0, 50);
// -- Fields --
/** Pane containing XML tree. */
protected JTabbedPane tabPane;
/** TemplateParser object.*/
protected TemplateParser tParse;
/** Keeps track of the OMENode being operated on currently.*/
protected OMENode thisOmeNode;
/** A list of all TablePanel objects. */
protected Vector panelList;
/** A list of TablePanel objects that have ID attributes. */
protected Vector panelsWithID;
/** A list of external references to be added to the combobox cell editor.*/
protected Vector addItems;
/** A list of the TabPanels.*/
protected Vector tabPanelList;
/**
* Hashtable containing internal semantic
* type defs in current file.
*/
public Hashtable internalDefs;
/**
* Signifies that the current file has
* changed from the last saved version.
*/
public boolean hasChanged;
/** If true, the save button should be display in each TabPanel.*/
protected boolean addSave;
/**Whether or not the user should be able to edit metadata.*/
protected boolean editable;
/**Whether or not to display ID attributes. By default, is false.*/
protected boolean showIDs;
/** Holds the original file if it is of TIFF format.*/
protected File originalTIFF;
/** Holds the currently edited file, or null if none.*/
protected File currentFile;
/** Holds the first image of a tiff file.*/
public BufferedImage img, thumb;
public BufferedImage[] images, thumbs;
/** Holds the image reader used to open image or null if none used. */
protected BufferedImageReader reader;
private int minPixNum;
private boolean pixelsIDProblem, isOMETiff;
protected String fileID;
protected Hashtable tiffDataStore;
protected OMEXMLMetadata ms;
// -- Fields - raw panel --
/** Panel containing raw XML dump. */
protected JPanel rawPanel;
/** Text area displaying raw XML. */
protected JTextArea rawText;
/** Whether XML is being displayed in raw form. */
protected boolean raw;
// -- Constructor --
/** Constructs default widget for displaying OME-XML metadata. */
public MetadataPane() { this((File) null, true); }
/**
* Constructs a pane to display the OME-XML metadata of a
* given file.
* @param file The file to be initially displayed.
*/
public MetadataPane(File file) { this(file, true); }
/**
* Constructs a pane to display the OME-XML metadata of a
* given file and with the given save policy.
* @param file The file to be initially displayed.
* @param save Whether saving should be allowed.
*/
public MetadataPane(File file, boolean save) { this(file, save, true); }
/**
* Constructs a pane to display the OME-XML metadata of a
* given file with the given save and editing policy.
* @param file the file to open initially
* @param save whether or not to display the save button
* @param editMe whether or not the user should be able to edit metadata
*/
public MetadataPane(File file, boolean save, boolean editMe) {
// -- General Field Initialization --
editable = editMe;
panelList = new Vector();
panelsWithID = new Vector();
addItems = new Vector();
tParse = new TemplateParser("Template.xml");
thisOmeNode = null;
internalDefs = null;
hasChanged = false;
currentFile = null;
addSave = save;
originalTIFF = null;
img = null;
thumb = null;
showIDs = false;
reader = null;
// -- Tabbed Pane Initialization --
tabPane = new JTabbedPane(JTabbedPane.TOP, JTabbedPane.WRAP_TAB_LAYOUT);
setupTabs();
setLayout(new CardLayout());
add(tabPane, "tabs");
setPreferredSize(new Dimension(700, 500));
tabPane.setVisible(true);
setVisible(true);
// -- Raw panel --
raw = false;
rawPanel = new JPanel();
rawPanel.setLayout(new BorderLayout());
// label explaining what happened
JLabel rawLabel = new JLabel("Metadata parsing failed. " +
"Here is the raw info. Good luck!");
rawLabel.setBorder(new EmptyBorder(5, 0, 5, 0));
rawPanel.add(rawLabel, BorderLayout.NORTH);
// text area for displaying raw XML
rawText = new JTextArea();
rawText.setLineWrap(true);
rawText.setColumns(50);
rawText.setRows(30);
rawText.setEditable(false);
rawPanel.add(new JScrollPane(rawText), BorderLayout.CENTER);
rawPanel.setVisible(false);
add(rawPanel, "raw");
//open initial file
if (file != null) {
setOMEXML(file);
if (getTopLevelAncestor() instanceof MetadataEditor) {
MetadataEditor mn = (MetadataEditor) getTopLevelAncestor();
mn.setCurrentFile(file);
}
}
}
// -- MetadataPane API methods --
/**
* Retrieves the current document object
* describing the whole OMEXMLNode tree.
*/
public Document getDoc() {
Document doc = null;
try { doc = thisOmeNode.getOMEDocument(false); }
catch (Exception e) { }
return doc;
}
/**
* Tells whether or not the XML has changed due to
* user manipulation.
*/
public boolean getState() { return hasChanged; }
/**
* Sets whether or not the XML has changed due to
* user manipulation.
* @param change Has the XML changed?
*/
public void stateChanged(boolean change) {
hasChanged = change;
for (int i = 0; i < tabPanelList.size(); i++) {
TabPanel thisTab = (TabPanel) tabPanelList.get(i);
if (change) thisTab.saveButton.setForeground(ADD_COLOR);
else thisTab.saveButton.setForeground(TEXT_COLOR);
}
}
/** Get the OMENode currently being edited.*/
public OMENode getRoot() { return thisOmeNode; }
public boolean testThirdParty(File file) {
String id = file.getPath();
if (!file.exists()) return false;
if (!isOMETiff) return false;
ImageReader read = new ImageReader();
try {
if (read.getReader(id) instanceof TiffReader ||
read.getReader(id) instanceof OMEXMLReader) return false;
else return false;
}
catch (FormatException exc) {
return true;
}
catch (IOException exc) {
return true;
}
}
public void askCompanionInstead(File file) {
Object[] options = {"Sounds good", "Cancel"};
int n = JOptionPane.showOptionDialog(getTopLevelAncestor(),
"The file you are trying to save to is a third-party format."
+ " Currently only TIFF or OME-XML files can be saved."
+ " Would you like to save to a companion file instead?",
"Can't Save to Third-Party Format",
JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE,
(javax.swing.Icon)null,
options,
options[0]);
if (n == JOptionPane.YES_OPTION) saveCompanionFile(file);
}
/** Save to the given file.*/
public void saveFile(File file) {
try {
//use the node tree in the MetadataPane to write flattened OMECA
//to a given file
if (testThirdParty(file)) {
askCompanionInstead(file);
return;
}
if (originalTIFF != null && !file.getPath().endsWith(".ome")) {
String xml = thisOmeNode.writeOME(false);
if (originalTIFF.equals(file)) {
//just rewrite image description of original file.
xml = addTiffData(xml, file);
String path = file.getAbsolutePath();
RandomAccessOutputStream out = new RandomAccessOutputStream(path);
try {
TiffSaver saver = new TiffSaver(out, path);
RandomAccessInputStream in = new RandomAccessInputStream(path);
saver.overwriteComment(in, xml);
in.close();
}
finally {
out.close();
}
}
else {
//create the new tiff file.
saveTiffFile(file);
}
}
else {
thisOmeNode.writeOME(file, false);
if (getTopLevelAncestor() instanceof MetadataEditor) {
MetadataEditor mdn = (MetadataEditor) getTopLevelAncestor();
mdn.setTitle("OME Metadata Editor - " + file);
}
}
}
catch (Exception e) {
//if all hell breaks loose, display an error dialog
JOptionPane.showMessageDialog(getTopLevelAncestor(),
"Sadly, the file you specified is either write-protected\n" +
"or in use by another program. Game over, man.",
"Unable to Write to Specified File", JOptionPane.ERROR_MESSAGE);
System.out.println("ERROR! Attempt failed to open file: " +
file.getName());
}
}
public void saveCompanionFile(File file) {
File compFile = new File(file.getPath() + ".ome");
if (compFile.exists()) compFile.delete();
try {
thisOmeNode.writeOME(compFile, false);
}
catch (Exception exc) {
if (exc instanceof RuntimeException) throw (RuntimeException) exc;
else exc.printStackTrace();
}
}
public void saveTiffFile(File file) {
if (originalTIFF != null && originalTIFF.equals(file)) saveFile(file);
else {
String id = currentFile.getPath();
String outId = id + ".tif";
File outFile = new File(outId);
if (outFile.exists()) outFile.delete();
if (reader == null) reader = new BufferedImageReader();
TiffWriter writer = new TiffWriter();
int imageCount = 0;
String xml = null;
try {
xml = thisOmeNode.writeOME(false);
xml = addTiffData(xml, file);
reader.setId(id);
imageCount = reader.getImageCount();
}
catch (Exception exc) {
if (exc instanceof RuntimeException) throw (RuntimeException) exc;
else exc.printStackTrace();
}
try {
ServiceFactory factory = new ServiceFactory();
OMEXMLService service =
(OMEXMLService) factory.getInstance(OMEXMLService.class);
IMetadata meta = service.createOMEXMLMetadata();
writer.setMetadataRetrieve(meta);
meta.setPixelsBinDataBigEndian(
new Boolean(!reader.isLittleEndian()), 0, 0);
try {
meta.setPixelsDimensionOrder(
DimensionOrder.fromString(reader.getDimensionOrder()), 0);
}
catch (EnumerationException e) { }
meta.setPixelsSizeX(
new PositiveInteger(new Integer(reader.getSizeX())), 0);
meta.setPixelsSizeY(
new PositiveInteger(new Integer(reader.getSizeY())), 0);
meta.setPixelsSizeZ(
new PositiveInteger(new Integer(reader.getSizeZ())), 0);
meta.setPixelsSizeC(
new PositiveInteger(new Integer(reader.getSizeC())), 0);
meta.setPixelsSizeT(
new PositiveInteger(new Integer(reader.getSizeT())), 0);
writer.setId(outId);
}
catch (Exception exc) {
if (exc instanceof RuntimeException) throw (RuntimeException) exc;
else exc.printStackTrace();
}
for(int i = 0; i < imageCount; i++) {
byte[] plane = null;
try {
plane = reader.openBytes(i);
}
catch (Exception exc) {
if (exc instanceof RuntimeException) throw (RuntimeException) exc;
else exc.printStackTrace();
}
IFD ifd = null;
if (i == 0) {
// save OME-XML metadata to TIFF file's first IFD
ifd = new IFD();
ifd.putIFDValue(IFD.IMAGE_DESCRIPTION, xml);
}
// write plane to output file
try {
writer.saveBytes(i, plane, ifd);
}
catch (Exception exc) {
if (exc instanceof RuntimeException) throw (RuntimeException) exc;
else exc.printStackTrace();
}
}
currentFile = new File(outId);
if (getTopLevelAncestor() instanceof MetadataEditor) {
MetadataEditor mn = (MetadataEditor) getTopLevelAncestor();
mn.setCurrentFile(file);
}
}
}
public void saveTiffFile(File file, String outId) {
String id = currentFile.getPath();
File outFile = new File(outId);
if (outFile.exists()) outFile.delete();
if (reader == null) reader = new BufferedImageReader();
TiffWriter writer = new TiffWriter();
int imageCount = 0;
String xml = null;
try {
xml = thisOmeNode.writeOME(false);
xml = addTiffData(xml, file);
reader.setId(id);
imageCount = reader.getImageCount();
}
catch (Exception exc) {
if (exc instanceof RuntimeException) throw (RuntimeException) exc;
else exc.printStackTrace();
}
for(int i = 0; i < imageCount; i++) {
byte[] plane = null;
try {
plane = reader.openBytes(i);
}
catch (Exception exc) {
if (exc instanceof RuntimeException) throw (RuntimeException) exc;
else exc.printStackTrace();
}
IFD ifd = null;
if (i == 0) {
// save OME-XML metadata to TIFF file's first IFD
ifd = new IFD();
ifd.putIFDValue(IFD.IMAGE_DESCRIPTION, xml);
}
// write plane to output file
try {
writer.setId(outId);
writer.saveBytes(i, plane, ifd);
}
catch (Exception exc) {
if (exc instanceof RuntimeException) throw (RuntimeException) exc;
else exc.printStackTrace();
}
}
currentFile = new File(outId);
if (getTopLevelAncestor() instanceof MetadataEditor) {
MetadataEditor mn = (MetadataEditor) getTopLevelAncestor();
mn.setCurrentFile(file);
}
}
public void merge() {
if (currentFile != null) {
String id = currentFile.getPath();
ImageReader read = new ImageReader();
OMEXMLMetadata oms =
(OMEXMLMetadata) MetadataTools.createOMEXMLMetadata();
read.setMetadataStore(oms);
try {
//just to repopulate the metadatastore to original state
read.setId(id);
int imageCount = read.getImageCount();
}
catch (Exception exc) {
if (exc instanceof RuntimeException) throw (RuntimeException) exc;
else exc.printStackTrace();
}
OMENode ome = (OMENode)oms.getRoot();
File companion = new File(currentFile.getPath() + ".ome");
if (companion.exists()) {
Merger merge = new Merger(ome, companion, this);
setOMEXML(merge.getRoot());
}
else {
JOptionPane.showMessageDialog(this,
"No companion file found to merge!!",
"MetadataEditor Error", JOptionPane.ERROR_MESSAGE);
}
}
else {
JOptionPane.showMessageDialog(this,
"You have not saved or opened a file to merge yet!",
"MetadataEditor Error", JOptionPane.ERROR_MESSAGE);
}
}
public void storeTiffData(File file) {
tiffDataStore = new Hashtable();
Document doc;
Vector pixList = new Vector();
DocumentBuilderFactory docFact =
DocumentBuilderFactory.newInstance();
try {
DocumentBuilder db = docFact.newDocumentBuilder();
// get TIFF comment without parsing out TiffData Elements
String comment = new TiffParser(currentFile.getPath()).getComment();
ByteArrayInputStream bis = new ByteArrayInputStream(comment.getBytes());
doc = db.parse((java.io.InputStream)bis);
pixList = DOMUtil.findElementList("Pixels", doc);
}
catch (IOException exc) {
exc.printStackTrace();
}
catch (org.xml.sax.SAXException exc) {
exc.printStackTrace();
}
catch (javax.xml.parsers.ParserConfigurationException exc) {
exc.printStackTrace();
}
for(int i = 0; i<pixList.size(); i++) {
Element thisEle = (Element) pixList.get(i);
String thisID = DOMUtil.getAttribute("ID", thisEle);
Vector dataList = DOMUtil.getChildElements("TiffData", thisEle);
Vector tiffDataAttrs = new Vector();
for(int j = 0; j<dataList.size(); j++) {
Element thisData = (Element) dataList.get(j);
String[] attrNames = DOMUtil.getAttributeNames(thisData);
String[] attrValues = DOMUtil.getAttributeValues(thisData);
Hashtable attrs = new Hashtable();
for(int k= 0; k<attrNames.length; k++) {
attrs.put(attrNames[k], attrValues[k]);
}
tiffDataAttrs.add(attrs);
}
tiffDataStore.put(thisID, tiffDataAttrs);
}
}
public String addTiffData(String xml, File file) {
Document doc = null;
Vector pixList = new Vector();
DocumentBuilderFactory docFact =
DocumentBuilderFactory.newInstance();
try {
DocumentBuilder db = docFact.newDocumentBuilder();
ByteArrayInputStream bis = new ByteArrayInputStream(xml.getBytes());
doc = db.parse((java.io.InputStream)bis);
pixList = DOMUtil.findElementList("Pixels", doc);
}
catch (IOException exc) {
exc.printStackTrace();
}
catch (org.xml.sax.SAXException exc) {
exc.printStackTrace();
}
catch (javax.xml.parsers.ParserConfigurationException exc) {
exc.printStackTrace();
}
//creating tiffData from non-OME-Tiff
if (!isOMETiff) {
for(int i = 0; i<pixList.size(); i++) {
Element thisEle = (Element) pixList.get(i);
DOMUtil.createChild(thisEle, "TiffData");
}
}
//creating tiff from OMETiff file
else if (isOMETiff) {
boolean prompted = false;
boolean addElements = false;
for(int i = 0; i<pixList.size(); i++) {
Element thisEle = (Element) pixList.get(i);
String thisID = DOMUtil.getAttribute("ID", thisEle);
Vector dataEles = (Vector) tiffDataStore.get(thisID);
//fixes if TiffData Elements not in File but should be
if (dataEles.size() == 0) {
if (!prompted) {
Object[] options =
{"Sounds good", "Cancel (Nothing bad will happen)"};
int n = JOptionPane.showOptionDialog(getTopLevelAncestor(),
"We detected that an OME-xml companion file exists for"
+ " the file you just opened, \n would you like to merge these"
+ " files in some manner?",
"Companion File Detected",
JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE,
(javax.swing.Icon)null,
options,
options[0]);
if (n == JOptionPane.YES_OPTION) addElements = true;
prompted = true;
}
if (addElements) {
DOMUtil.createChild(thisEle, "TiffData");
continue;
}
}
for(int j=0; j<dataEles.size(); j++) {
Element thisData = DOMUtil.createChild(thisEle, "TiffData");
Hashtable attrs = (Hashtable) dataEles.get(j);
Object[] attrKeys = attrs.keySet().toArray();
for(int k = 0; k<attrKeys.length; k++) {
String name = (String)attrKeys[k];
String value = (String)(attrs.get(name));
if (value == null) value = "";
DOMUtil.setAttribute(name, value, thisData);
}
}
}
}
String result = null;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DOMUtil.writeXML(baos, doc);
result = baos.toString();
}
catch (Exception exc) {
exc.printStackTrace();
}
return result;
}
public boolean checkOMETiff(File file) {
try {
String comment = new TiffParser(file.getPath()).getComment();
OMENode testNode = new OMENode(comment);
}
catch (IOException exc) {
return false;
}
catch (javax.xml.parsers.ParserConfigurationException exc) {
return false;
}
catch (org.xml.sax.SAXException exc) {
return false;
}
catch (javax.xml.transform.TransformerConfigurationException exc) {
return false;
}
catch (javax.xml.transform.TransformerException exc) {
return false;
}
return true;
}
/**
* Sets the displayed OME-XML metadata to correspond
* to the given character string of XML.
*/
private void setOMEXML(String xml) {
OMENode ome = null;
try { ome = new OMENode(xml); }
catch (Exception exc) { }
raw = ome == null;
if (raw) rawText.setText(xml);
else setOMEXML(ome);
SwingUtilities.invokeLater(this);
}
/**
* Sets the displayed OME-XML metadata to correspond
* to the given OME-XML or OME-TIFF file.
* @return true if the operation was successful
*/
public boolean setOMEXML(File file) {
try {
RandomAccessInputStream in =
new RandomAccessInputStream(file.getAbsolutePath());
TiffParser parser = new TiffParser(in);
isOMETiff = false;
if (parser.isValidHeader()) {
// TIFF file
originalTIFF = file;
}
else originalTIFF = null;
in.close();
OMENode ome = null;
boolean doMerge = false;
try {
reader = new BufferedImageReader();
ms = (OMEXMLMetadata) MetadataTools.createOMEXMLMetadata();
// tell reader to write metadata as it's being
// parsed to an OMENode (DOM in memory)
reader.setMetadataStore(ms);
String id = file.getPath();
fileID = id;
currentFile = file;
File companionFile = new File(id + ".ome");
if (companionFile.exists()) {
Object[] options = {"Sounds good", "No, open original file"};
int n = JOptionPane.showOptionDialog(getTopLevelAncestor(),
"We detected that an OME-xml companion file exists for"
+ " the file you just opened, \n would you like to merge these"
+ " files in some manner?",
"Companion File Detected",
JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE,
(javax.swing.Icon)null,
options,
options[0]);
if (n == JOptionPane.YES_OPTION) doMerge = true;
}
//Set up thumbnails
reader.setId(id);
int numSeries = reader.getSeriesCount();
images = new BufferedImage[numSeries+1];
thumbs = new BufferedImage[numSeries+1];
for(int i = 0; i<numSeries; i++) {
if (numSeries > 1) reader.setSeries(i);
int num = reader.getImageCount();
if (num > 0) {
// get middle image from the file
img = reader.openImage(num / 2);
}
else img = null;
images[i] = img;
int width = 50, height = 50;
thumb = AWTImageTools.scale(img, width, height, false);
thumbs[i] = thumb;
}
ome = (OMENode) ms.getRoot();
if (doMerge) {
Merger merge = new Merger(ome, companionFile, this);
ome = merge.getRoot();
}
//handle if tiff after reader has been constructed
if (originalTIFF != null) {
isOMETiff = checkOMETiff(file);
storeTiffData(file);
}
//find minimum pixel ID, doesn't have to be zero, if not in
//standard format, flag this, all thumbs will be the same
minPixNum = 0;
pixelsIDProblem = false;
Vector pixList = new Vector();
try {
pixList = DOMUtil.findElementList("Pixels", ome.getOMEDocument(true));
int lowestInt = -1;
for(int i = 0; i<pixList.size(); i++) {
Element thisPix = (Element) pixList.get(i);
String thisID = thisPix.getAttribute("ID");
int colonIndex = thisID.indexOf(":");
if (colonIndex == -1) {
pixelsIDProblem = true;
break;
}
String pixNumString = thisID.substring(colonIndex + 1);
int pixNum = -1;
try {
pixNum = Integer.parseInt(pixNumString);
}
catch (java.lang.NumberFormatException exc) {
pixelsIDProblem = true;
break;
}
if (lowestInt == -1) {
lowestInt = pixNum;
continue;
}
if (lowestInt > pixNum) lowestInt = pixNum;
}
minPixNum = lowestInt;
if (minPixNum == -1) pixelsIDProblem = true;
}
catch (Exception exc) { exc.printStackTrace(); }
if (pixList.size() == 1) pixelsIDProblem = false;
setOMEXML(ome);
}
catch (FormatException exc) {
if ("Unsupported ZCT index mapping".equals(exc.getMessage())) {
JOptionPane.showMessageDialog(this,
"This tiff file is corrupted. The ZCT index mapping is"
+ " unsupported by bioformats.\nYour metadata will not"
+ " populate correctly, our apologies.",
"MetadataEditor Error", JOptionPane.ERROR_MESSAGE);
}
img = null;
thumb = null;
String xml = DataTools.readFile(file.getAbsolutePath());
if (xml.startsWith("<?xml") || xml.startsWith("<OME")) {
setOMEXML(xml);
}
else return false;
}
catch (IOException exc) { exc.printStackTrace(); };
in.close();
return true;
}
catch (IOException exc) { return false; }
}
/** Sets the displayed OME-XML metadata. */
protected void setOMEXML(OMENode ome) {
// test for document, then call the setup(OMENode ome) method
Document doc = null;
try { doc = ome == null ? null : ome.getOMEDocument(false); }
catch (Exception exc) { }
if (doc == null) {
JOptionPane.showMessageDialog(this,
"Document is NULL.",
"MetadataEditor Error", JOptionPane.ERROR_MESSAGE);
System.out.println("Document is NULL.");
return;
}
internalDefs = new Hashtable();
//time to parse internal semantic type defs in file
//to handle appropriate reference types
Element thisRoot = ome.getDOMElement();
NodeList nl = thisRoot.getChildNodes();
for (int j = 0; j < nl.getLength(); j++) {
Node node = nl.item(j);
if (!(node instanceof Element)) continue;
Element someE = (Element) node;
if (!someE.getTagName().equals("STD:SemanticTypeDefinitions")) {
continue;
}
NodeList omeEleList = node.getChildNodes();
for (int k = 0; k < omeEleList.getLength(); k++) {
node = omeEleList.item(k);
if (!(node instanceof Element)) continue;
Element omeEle = (Element) node;
if (!omeEle.getTagName().equals("SemanticType")) continue;
NodeList omeAttrList = node.getChildNodes();
Hashtable thisHash = new Hashtable(10);
for (int l = 0; l < omeAttrList.getLength(); l++) {
node = omeAttrList.item(l);
if (!(node instanceof Element)) continue;
Element omeAttr = (Element) node;
if (!omeAttr.getTagName().equals("Element")) continue;
if (!omeAttr.hasAttribute("DataType")) continue;
String dType = omeAttr.getAttribute("DataType");
if (!dType.equals("reference")) continue;
String attrName = omeAttr.getAttribute("Name");
String refType = omeAttr.getAttribute("RefersTo");
thisHash.put(attrName, refType);
}
internalDefs.put(omeEle.getAttribute("Name"), thisHash);
}
}
thisOmeNode = ome;
stateChanged(false);
setupTabs(ome);
}
/**
* Sets up the JTabbedPane based on a template, assumes that no OMEXML
* file is being compared to the template, so no data will be displayed.
* should be used to initialize the application and to create new OMEXML
* documents based on the template
*/
public void setupTabs() {
//make sure all old gui stuff is tossed when this called twice.
//also clear our TablePanel lists
tabPanelList = new Vector();
panelList = new Vector();
panelsWithID = new Vector();
addItems = new Vector();
tabPane.removeAll();
currentFile = null;
originalTIFF = null;
img = null;
thumb = null;
internalDefs = new Hashtable();
try { thisOmeNode = new OMENode(); }
catch (Exception e) { e.printStackTrace(); }
//use the list acquired from Template.xml to form the initial tabs
Element[] tabList = tParse.getTabs();
for (int i = 0; i< tabList.length; i++) {
String thisName = tabList[i].getAttribute("Name");
if (thisName.length() == 0) thisName = tabList[i].getAttribute("XMLName");
//make a TabPanel Object that represents the panel
//that displays data for a node
TabPanel tPanel = new TabPanel(tabList[i]);
//set the field oNode in TabPanel to reflect the structure of the xml
//document being formed
//do all the good stuff to flesh out the TabPanel's gui with the tables
//and assorted stuff
renderTab(tPanel);
//set up a scrollpane to hold the TabPanel in case it's too large to fit
JScrollPane scrollPane = new JScrollPane(tPanel);
scrollPane.setVerticalScrollBarPolicy(
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
scrollPane.setHorizontalScrollBarPolicy(
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
String desc = tabList[i].getAttribute("Description");
if (desc.length() == 0) tabPane.addTab(thisName, NO_DATA_BULLET,
scrollPane, null);
else tabPane.addTab(thisName, NO_DATA_BULLET, scrollPane, desc);
int keyNumber = getKey(i+1);
if (keyNumber !=0) tabPane.setMnemonicAt(i, keyNumber);
}
//Makes sure that the external references do not mirror the internal ones
//since there should be no intersection between the two sets
for (int j = 0; j<panelsWithID.size(); j++) {
TablePanel tempTP = (TablePanel) panelsWithID.get(j);
String tryID = "(External) " + tempTP.id;
if (addItems.indexOf(tryID) >= 0) addItems.remove(tryID);
}
//this part sets up the refTable's comboBox editor to have choices
//corresponding to every TablePanel that has a valid ID attribute
for (int i = 0; i<panelList.size(); i++) {
TablePanel p = (TablePanel) panelList.get(i);
p.setEditor();
}
//set the displayed tab to be by default the first image
TabPanel firstImageTab = null;
for (int i=0; i<tabPanelList.size(); i++) {
TabPanel tabP = (TabPanel) tabPanelList.get(i);
if (tabP.name.startsWith("Image")) {
firstImageTab = tabP;
break;
}
}
if (firstImageTab != null) {
Container anObj = (Container) firstImageTab;
while (!(anObj instanceof JScrollPane)) {
anObj = anObj.getParent();
}
JScrollPane jScr = (JScrollPane) anObj;
tabPane.setSelectedComponent(jScr);
}
stateChanged(false);
}
/**
* sets up the JTabbedPane given an OMENode from an OMEXML file.
* the template will set which parts of the file are displayed.
*/
public void setupTabs(OMENode ome) {
//Get rid of old gui components and old TablePanel lists
//when new file is opened
tabPane.removeAll();
tabPanelList = new Vector();
panelList = new Vector();
panelsWithID = new Vector();
addItems = new Vector();
//use the list acquired from Template.xml to form the initial tabs
Element[] tabList = tParse.getTabs();
Vector actualTabs = new Vector(2 * tabList.length);
Vector oNodeList = new Vector(2 * tabList.length);
for (int i = 0; i< tabList.length; i++) {
//since we have an OMEXML file to compare to now, we have to worry about
//repeat elements
//inOmeList will hold all instances of a particular tagname on one level
//of the node-tree
Vector inOmeList = null;
String aName = tabList[i].getAttribute("XMLName");
//work-around checks if we need to look in CustomAttributes,
//and subsequently ignore it
if (aName.equals("Image") || aName.equals("Feature") ||
aName.equals("Dataset") || aName.equals("Project"))
{
inOmeList = ome.getChildNodes(aName);
}
else {
if (ome.getChildNode("CustomAttributes") != null)
inOmeList = ome.getChildNode("CustomAttributes").getChildNodes(aName);
}
int vSize = 0;
if (inOmeList != null) vSize = inOmeList.size();
//check to see if one or more elements with the given tagname
//(a.k.a. "XMLName") exist in the file
if (vSize >0) {
for (int j = 0; j<vSize; j++) {
String thisName = tabList[i].getAttribute("Name");
if (thisName.length() == 0) {
thisName = tabList[i].getAttribute("XMLName");
}
//create our friend, the TabPanel, which holds the template Element
//and the actual OMEXMLNode that either existed previously or has
//been created by the ReflectedUniverse mumbojumbo
TabPanel tPanel = new TabPanel(tabList[i]);
tPanel.oNode = (OMEXMLNode) inOmeList.get(j);
//call renderTab(TabPanel tp) to set up the TabPanel gui, create
//TablePanels to display Elements, etc.
renderTab(tPanel);
//create a scrollpane to hold the tabpanel in case it is too large
JScrollPane scrollPane = new JScrollPane(tPanel);
scrollPane.setVerticalScrollBarPolicy(
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
scrollPane.setHorizontalScrollBarPolicy(
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
//test if a description is associated with this tab in the template
String desc = tabList[i].getAttribute("Description");
thisName = getTreePathName(tPanel.el, tPanel.oNode);
if (desc.length() == 0) {
tabPane.addTab(thisName, DATA_BULLET, scrollPane, null);
}
else tabPane.addTab(thisName, DATA_BULLET, scrollPane, desc);
actualTabs.add(tabList[i]);
oNodeList.add(tPanel.oNode);
}
}
//if no instances of tagname in file,
//still set up a blank table based on the template
else {
//this section the same as above case, see comments there
String thisName = tabList[i].getAttribute("Name");
if (thisName.length() == 0) {
thisName = tabList[i].getAttribute("XMLName");
}
TabPanel tPanel = new TabPanel(tabList[i]);
renderTab(tPanel);
JScrollPane scrollPane = new JScrollPane(tPanel);
scrollPane.setVerticalScrollBarPolicy(
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
scrollPane.setHorizontalScrollBarPolicy(
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
String desc = tabList[i].getAttribute("Description");
if (desc.length() == 0) {
tabPane.addTab(thisName, NO_DATA_BULLET, scrollPane, null);
}
else tabPane.addTab(thisName, NO_DATA_BULLET, scrollPane, desc);
actualTabs.add(tabList[i]);
oNodeList.add(tPanel.oNode);
}
}
//set up mnemonics, and form an array holding the names of the tabs
String[] tabNames = new String[actualTabs.size()];
for (int i = 0; i<actualTabs.size(); i++) {
int keyNumber = getKey(i+1);
if (keyNumber !=0) tabPane.setMnemonicAt(i, keyNumber);
Element e = (Element) actualTabs.get(i);
tabNames[i] = getTreePathName(e, (OMEXMLNode) oNodeList.get(i));
if (tabNames[i] == null) tabNames[i] = getTreePathName(e);
}
//change the "Tabs" menu in the original window to reflect the actual tabs
//created (duplicate tabs are the reason for this)
if (getTopLevelAncestor() instanceof MetadataEditor) {
MetadataEditor mn = (MetadataEditor) getTopLevelAncestor();
mn.changeTabMenu(tabNames);
}
//Makes sure that the external references do not mirror the internal ones
//since there should be no intersection between the two sets
for (int j = 0; j<panelsWithID.size(); j++) {
TablePanel tempTP = (TablePanel) panelsWithID.get(j);
String tryID = "(External) " + tempTP.id;
if (addItems.indexOf(tryID) >= 0) addItems.remove(tryID);
}
//this part sets up the refTable's comboBox editor to have choices
//corresponding to every TablePanel that has a valid ID attribute
for (int i = 0; i<panelList.size(); i++) {
TablePanel p = (TablePanel) panelList.get(i);
p.setEditor();
}
//set the displayed tab to be by default the first image
TabPanel firstImageTab = null;
for (int i=0; i<tabPanelList.size(); i++) {
TabPanel tabP = (TabPanel) tabPanelList.get(i);
if (tabP.name.startsWith("Image")) {
firstImageTab = tabP;
break;
}
}
if (firstImageTab != null) {
Container anObj = (Container) firstImageTab;
while (!(anObj instanceof JScrollPane)) {
anObj = anObj.getParent();
}
JScrollPane jScr = (JScrollPane) anObj;
tabPane.setSelectedComponent(jScr);
}
}
/**
* Fleshes out the GUI of a given TabPanel, adding TablePanels appropriately.
*/
public void renderTab(TabPanel tp) {
if (tp.isRendered) return;
tp.isRendered = true;
tp.removeAll();
Vector iHoldTables = new Vector();
//add a title label to show which element
JPanel titlePanel = new JPanel();
titlePanel.setLayout(new GridLayout(2, 1));
JLabel title = new JLabel();
Font thisFont = title.getFont();
Font newFont = new Font(thisFont.getFontName(), Font.BOLD, 18);
title.setFont(newFont);
if (tp.oNode != null) {
title.setText(" " + getTreePathName(tp.el, tp.oNode) + ":");
}
else title.setText(" " + getTreePathName(tp.el) + ":");
title.setForeground(new Color(255, 255, 255));
tp.saveButton = new JButton("QuickSave");
tp.saveButton.setPreferredSize(new Dimension(100, 17));
tp.saveButton.setActionCommand("save");
tp.saveButton.addActionListener(this);
tp.saveButton.setOpaque(false);
tp.saveButton.setForeground(TEXT_COLOR);
if (getState()) tp.saveButton.setForeground(ADD_COLOR);
if (!addSave) tp.saveButton.setVisible(false);
Color aColor = getBackground();
JTextArea descrip = new JTextArea();
//if title has a description, add it in italics
if (tp.el.hasAttribute("Description")) {
if (tp.el.getAttribute("Description").length() != 0) {
descrip.setEditable(false);
descrip.setLineWrap(true);
descrip.setWrapStyleWord(true);
descrip.setBackground(aColor);
newFont = new Font(thisFont.getFontName(),
Font.ITALIC, thisFont.getSize());
descrip.setFont(newFont);
descrip.setText(" " + tp.el.getAttribute("Description"));
}
}
FormLayout myLayout = new FormLayout(
"pref, 5dlu, pref:grow:right, 5dlu",
"5dlu, pref, 5dlu, pref");
PanelBuilder build = new PanelBuilder(myLayout);
CellConstraints cellC = new CellConstraints();
build.add(title, cellC.xy(1, 2, "left, center"));
build.add(tp.saveButton, cellC.xy(3, 2, "right, center"));
build.add(descrip, cellC.xyw(1, 4, 4, "fill, center"));
titlePanel = build.getPanel();
titlePanel.setBackground(TEXT_COLOR);
//this sets up titlePanel so we can access its height later to
//use for "Goto" button scrollpane view setting purposes
tp.titlePanel = titlePanel;
//First instantiation of TablePanel. This one corresponds to the
//actual "top-level" element, e.g. what the main Tab element is
TablePanel pan = new TablePanel(tp.el, tp, tp.oNode);
iHoldTables.add(pan);
//make the nested elements have a further indent to distinguish them
//look at the template to get the nested Elements we need to display
//with their own TablePanels
Vector theseElements = DOMUtil.getChildElements("OMEElement", tp.el);
//will be a list of those nested elements that have their own nested
//elements
Vector branchElements = new Vector(theseElements.size());
//check out each nested Element
for (int i = 0; i<theseElements.size(); i++) {
Element e = null;
if (theseElements.get(i) instanceof Element) {
e = (Element) theseElements.get(i);
}
if (DOMUtil.getChildElements("OMEElement", e).size() != 0) {
branchElements.add(e);
}
else {
if (tp.oNode != null) {
Vector v = new Vector();
String aName = e.getAttribute("XMLName");
if (aName.equals("Image") || aName.equals("Feature") ||
aName.equals("Dataset") || aName.equals("Project"))
{
v = DOMUtil.getChildElements(aName, tp.oNode.getDOMElement());
}
else if (tp.oNode.getChildNode("CustomAttributes") != null) {
v = DOMUtil.getChildElements(aName,
tp.oNode.getChildNode("CustomAttributes").getDOMElement());
}
if (v.size() == 0) {
OMEXMLNode n = null;
TablePanel p = new TablePanel(e, tp, n);
iHoldTables.add(p);
}
else {
for (int j = 0; j<v.size(); j++) {
Element anEle = (Element) v.get(j);
OMEXMLNode n = null;
String unknownName = aName;
try {
ReflectedUniverse r = new ReflectedUniverse();
if (unknownName.equals("Project") ||
unknownName.equals("Feature") ||
unknownName.equals("CustomAttributes") ||
unknownName.equals("Dataset") ||
unknownName.equals("Image"))
{
r.exec("import org.openmicroscopy.xml." +
unknownName + "Node");
r.setVar("DOMElement", anEle);
r.exec("result = new " + unknownName + "Node(DOMElement)");
n = (OMEXMLNode) r.getVar("result");
}
else {
r.exec("import org.openmicroscopy.xml.st." +
unknownName + "Node");
r.setVar("DOMElement", anEle);
r.exec("result = new " + unknownName + "Node(DOMElement)");
n = (OMEXMLNode) r.getVar("result");
}
}
catch (Exception exc) {
//System.out.println(exc.toString());
}
if (n == null) {
n = new AttributeNode(anEle);
}
TablePanel p = new TablePanel(e, tp, n);
iHoldTables.add(p);
}
}
}
else {
OMEXMLNode n = null;
TablePanel p = new TablePanel(e, tp, n);
iHoldTables.add(p);
}
}
}
String rowString = "pref, 10dlu, ";
for (int i = 0; i<iHoldTables.size(); i++) {
rowString = rowString + "pref, 5dlu, ";
}
rowString = rowString.substring(0, rowString.length() - 2);
FormLayout layout = new FormLayout(
"5dlu, 5dlu, pref:grow, 5dlu, 5dlu",
rowString);
tp.setLayout(layout);
CellConstraints cc = new CellConstraints();
tp.add(titlePanel, cc.xyw(1, 1, 5));
int row = 1;
for (int i = 0; i<iHoldTables.size(); i++) {
row = row + 2;
Component c = (Component) iHoldTables.get(i);
tp.add(c, cc.xyw(i == 0 ? 2 : 3, row, 2, "fill, center"));
}
//Layout stuff distinguishes between the title and the data panels
}
/** changes the selected tab to tab of index i */
public void tabChange(int i) {
tabPane.setSelectedIndex(i);
}
/** resets all gui to reflect changes to the node tree*/
public void reRender() {
int tabIndex = tabPane.getSelectedIndex();
setupTabs(thisOmeNode);
tabPane.setSelectedIndex(tabIndex);
}
// -- Runnable API methods --
/** Shows or hides the proper subpanes. */
public void run() {
tabPane.setVisible(!raw);
rawPanel.setVisible(raw);
validate();
repaint();
}
// -- Event API methods --
/** Handle the "QuickSave" button actions.*/
public void actionPerformed(ActionEvent e) {
String cmd = e.getActionCommand();
if (cmd.equals("save")) {
if (currentFile != null) {
saveFile(currentFile);
stateChanged(false);
}
else JOptionPane.showMessageDialog(getTopLevelAncestor(),
"There is no current file specified, \n" +
"so you cannot QuickSave.",
"No Current File Found", JOptionPane.ERROR_MESSAGE);
}
}
// -- Static methods --
/** Returns an ImageIcon, or null if the path was invalid. */
protected static ImageIcon createImageIcon(String path, String description) {
java.net.URL imgURL = MetadataPane.class.getResource(path);
if (imgURL != null) {
return new ImageIcon(imgURL, description);
}
else {
System.err.println("Couldn't find file: " + path);
return null;
}
}
/** Get a path name for an element in the template.*/
public static String getTreePathName(Element e) {
String thisName = null;
if (e.hasAttribute("Name"))thisName = e.getAttribute("Name");
else thisName = e.getAttribute("XMLName");
Element aParent = DOMUtil.getAncestorElement("OMEElement", e);
while (aParent != null) {
if (aParent.hasAttribute("Name")) {
thisName = aParent.getAttribute("Name") + thisName;
}
else thisName = aParent.getAttribute("XMLName") + ": " + thisName;
aParent = DOMUtil.getAncestorElement("OMEElement", aParent);
}
return thisName;
}
/**
* Get a path name for a given OMEXMLNode found in the current
* file being edited based on the template. Only by calling this
* method will duplicate OMEXMLNodes be accounted for, e.g. this
* method adds Project (1) or Project (2) to signify multiple
* elements.
* @param el The template element that corresponds to this OMEXMLNode.
* @param on The OMEXMLNode that we want the path name for.
* @return A string representing the OMEXMLNode's path name.
*/
public static String getTreePathName(Element el, OMEXMLNode on) {
if (el != null && on != null) {
Vector pathList = new Vector();
Element aParent = on.getDOMElement();
Vector pathNames = getTreePathList(el);
pathNames.add("OME");
pathList.add(aParent);
for (int i = 1; i<pathNames.size(); i++) {
String s = (String) pathNames.get(i);
aParent = DOMUtil.getAncestorElement(s, aParent);
pathList.add(0, aParent);
}
String result = "";
for (int i = 0; i<pathList.size() - 1; i++) {
aParent = (Element) pathList.get(i);
Element aChild = (Element) pathList.get(i+1);
String thisName = aChild.getTagName();
NodeList nl = aParent.getElementsByTagName(thisName);
if (nl.getLength() == 1) {
Element e = (Element) nl.item(0);
if (i == 0) result = result + e.getTagName();
else result = result + ": " + e.getTagName();
}
else {
for (int j = 0; j<nl.getLength(); j++) {
Element e = (Element) nl.item(j);
if (e == aChild) {
Integer aInt = new Integer(j+1);
if (!result.equals("")) {
result += ": " + e.getTagName() + " (" + aInt + ")";
}
else result += e.getTagName() + " (" + aInt + ")";
}
}
}
}
return result;
}
else return null;
}
/**
* Tests if the given tagname should be
* placed under a CustomAttributesNode.
*/
public static boolean isInCustom(String tagName) {
if (tagName.equals("Project") ||
tagName.equals("Feature") ||
tagName.equals("CustomAttributes") ||
tagName.equals("Dataset") ||
tagName.equals("Image"))
{
return false;
}
else return true;
}
/**
* Return a new node of type specified by unknownName
* with the specified parent.
* N.B. The Parent can either be the direct parent or the
* ancestor that has the CustomAttributesNode that
* is the real parent.
* @param unknownName The name this new OMEXMLNode should have.
* @param parent The parent of the new OMEXMLNode. NEVER pass in
* a CustomAttributesNode, but rather the parent of that CANode.
*/
public static OMEXMLNode makeNode(String unknownName, OMEXMLNode parent) {
OMEXMLNode n = null;
CustomAttributesNode caNode = null;
try {
ReflectedUniverse r = new ReflectedUniverse();
if (!isInCustom(unknownName)) {
r.exec("import org.openmicroscopy.xml." +
unknownName + "Node");
r.setVar("parent", parent);
r.exec("result = new " + unknownName + "Node(parent)");
n = (OMEXMLNode) r.getVar("result");
}
else {
caNode = (CustomAttributesNode) parent.getChildNode("CustomAttributes");
if (caNode != null) {
r.exec("import org.openmicroscopy.xml.CustomAttributesNode");
r.exec("import org.openmicroscopy.xml.st." +
unknownName + "Node");
r.setVar("parent", caNode);
r.exec("result = new " + unknownName + "Node(parent)");
n = (OMEXMLNode) r.getVar("result");
}
else {
Element cloneEle = DOMUtil.createChild(
parent.getDOMElement(), "CustomAttributes");
caNode = new CustomAttributesNode(cloneEle);
r.exec("import org.openmicroscopy.xml.CustomAttributesNode");
r.exec("import org.openmicroscopy.xml.st." +
unknownName + "Node");
r.setVar("parent", caNode);
r.exec("result = new " + unknownName + "Node(parent)");
n = (OMEXMLNode) r.getVar("result");
}
}
}
catch (Exception exc) {
//System.out.println(exc.toString());
}
if (caNode != null && n == null) n = new AttributeNode(caNode, unknownName);
return n;
}
/**
* Returns a vector of Strings representing the XMLNames of the
* template's ancestors in ascending order in the list.
* @param e The template element we want the path list for.
* @return A vector holding the tagnames of all parent elements.
*/
public static Vector getTreePathList(Element e) {
Vector thisPath = new Vector(10);
thisPath.add(e.getAttribute("XMLName"));
Element aParent = DOMUtil.getAncestorElement("OMEElement", e);
while (aParent != null) {
thisPath.add(aParent.getAttribute("XMLName"));
aParent = DOMUtil.getAncestorElement("OMEElement", aParent);
}
return thisPath;
}
/**Converts a number into the KeyEvent for that number.*/
public static int getKey(int i) {
int keyNumber = 0;
switch (i) {
case 1:
keyNumber = KeyEvent.VK_1;
break;
case 2:
keyNumber = KeyEvent.VK_2;
break;
case 3:
keyNumber = KeyEvent.VK_3;
break;
case 4:
keyNumber = KeyEvent.VK_4;
break;
case 5:
keyNumber = KeyEvent.VK_5;
break;
case 6:
keyNumber = KeyEvent.VK_6;
break;
case 7:
keyNumber = KeyEvent.VK_7;
break;
case 8:
keyNumber = KeyEvent.VK_8;
break;
case 9:
keyNumber = KeyEvent.VK_9;
break;
case 10:
keyNumber = KeyEvent.VK_0;
break;
default:
keyNumber = 0;
}
return keyNumber;
}
// -- Helper classes --
/**
* Helper class to make my life easier in the creation and use of tabs
* associates a given xml template element and also an optional OMEXMLNode
* with a JPanel that represents the content of a tab.
*/
public class TabPanel extends JPanel
implements Scrollable
{
/**The template element this TabPanel corresponds to.*/
protected Element el;
/**The name of this TabPanel.*/
public String name;
/**Whether or not this TabPanel has already been rendered.*/
private boolean isRendered;
/**
* The OMEXMLNode in the current file that this TabPanel
* corresponds to.
*/
protected OMEXMLNode oNode;
/** The OMENode associated with this file*/
protected OMENode ome;
/** The JPanel that holds this TabPanel's title header.*/
protected JPanel titlePanel;
/** The "QuickSave" button for this TabPanel*/
protected JButton saveButton;
/** Construct a TabPanel arround a given template Element.*/
public TabPanel(Element el) {
ome = thisOmeNode;
isRendered = false;
this.el = el;
oNode = null;
name = getTreePathName(el);
titlePanel = null;
tabPanelList.add(this);
saveButton = null;
}
/** Convert this TabPanel into a String.*/
public String toString() { return el == null ? "null" :
"Name: " + name + " Element: " + el.getTagName(); }
/** Implement these scrollable methods to make resizing behave.*/
public Dimension getPreferredScrollableViewportSize() {
return getPreferredSize();
}
public int getScrollableUnitIncrement(Rectangle visibleRect,
int orientation, int direction)
{
return 5;
}
public int getScrollableBlockIncrement(Rectangle visibleRect,
int orientation, int direction)
{
return visibleRect.height;
}
public boolean getScrollableTracksViewportWidth() {
return true;
}
public boolean getScrollableTracksViewportHeight() {
return false;
}
}
/**
* Helper class to handle the various TablePanels that will be created to
* display the attributes of Elements that have no nested Elements
*/
public class TablePanel extends JPanel
implements ActionListener, MouseListener
{
/**The OMEXMLNode that this TablePanel is displaying.*/
public OMEXMLNode oNode;
/**The TabPanel that this TablePanel is a part of.*/
public TabPanel tPanel;
/**The NotePanel that holds the notes for this TablePanel.*/
public NotePanel noteP;
/**
* The "ID" OMEXML attribute for the OMEXMLNode this TablePanel
* displays.
*/
public String id;
/**The name of this TablePanel.*/
public String name;
/**
* An String to hold misc data we want to display to distinguish
* the name presented for this as an internal reference.
*/
public String refDetails;
/**The ClickableTable that displays this TablePanel's metadata.*/
public ClickableTable table;
/**The tableheader of this TablePanel's ClickableTable*/
JTableHeader tHead;
/**The template element that this TablePanel corresponds to.*/
public Element el;
/**Indicates whether or not this is a nested OMEXMLNode*/
public boolean isTopLevel;
/**
* The notes, add table, and delete table buttons
* of this TablePanel.
*/
protected JButton noteButton, addButton, delButton;
/**A list of non "Ref" type attributes.*/
protected Vector attrList;
/**A list of "Ref" type attributes.*/
protected Vector refList;
/**The JLabels for the title and the optional image.*/
protected JLabel tableName, imageLabel;
protected BufferedImage tableThumb;
protected BufferedImage tableImage;
/**
* Construct a TablePanel to display the metadata of a
* particular OMEXMLNode.
* @param e The template element this TablePanel corresponds to.
* @param tp The TabPanel this TablePanel is a part of.
* @param on The OMEXMLNode to be displayed.
*/
public TablePanel(Element e, TabPanel tp, OMEXMLNode on) {
isTopLevel = false;
//check if this TablePanel is "top level"
if (tp.oNode == null) {
Vector foundEles = DOMUtil.getChildElements("OMEElement",
tParse.getRoot());
for (int i = 0; i < foundEles.size(); i++) {
Element thisNode = (Element) foundEles.get(i);
if (thisNode == e) isTopLevel = true;
}
}
else if (tp.oNode != null && tp.oNode == on) isTopLevel = true;
el = e;
oNode = on;
tPanel = tp;
id = null;
JComboBox comboBox = null;
if (on != null) name = getTreePathName(e, on);
else name = getTreePathName(e);
String thisName = name;
panelList.add(this);
//for debuging this simple parser
final boolean debug = false;
//Check which "types" the various template attributes are and
//group them into Vectors.
Vector fullList = DOMUtil.getChildElements("OMEAttribute", e);
attrList = new Vector();
refList = new Vector();
for (int i = 0; i<fullList.size(); i++) {
Element thisE = (Element) fullList.get(i);
if (thisE.hasAttribute("Type")) {
if (thisE.getAttribute("Type").equals("Ref")) {
if (oNode != null) {
String value = oNode.getAttribute(thisE.getAttribute("XMLName"));
if (value != null && !value.equals("")) {
if (addItems.indexOf("(External) " + value) < 0) {
addItems.add("(External) " + value);
}
}
}
refList.add(thisE);
}
else if (thisE.getAttribute("Type").equals("ID") && oNode != null
&& !showIDs) {
if (oNode.getDOMElement().hasAttribute("ID")) {
id = oNode.getAttribute("ID");
panelsWithID.add(this);
}
}
else if (thisE.getAttribute("Type").equals("ID") && oNode != null
&& showIDs) {
if (oNode.getDOMElement().hasAttribute("ID")) {
id = oNode.getAttribute("ID");
panelsWithID.add(this);
attrList.add(thisE);
}
}
else attrList.add(thisE);
}
else attrList.add(thisE);
}
//Set up the details for internal reference names
refDetails = e.getAttribute("RefVars");
if (debug) System.out.println();
if (debug) System.out.println(name + " - " + refDetails);
boolean noDetails = true;
int openIndex = refDetails.indexOf('%');
while (openIndex >= 0) {
if (debug) {
System.out.println(openIndex + " " + refDetails.charAt(openIndex));
}
int closeIndex = refDetails.indexOf('%', openIndex + 1);
if (debug) {
System.out.println(closeIndex + " " + refDetails.charAt(closeIndex));
}
String thisCommand = refDetails.substring(openIndex + 1, closeIndex);
if (debug) {
System.out.println("Command: " + thisCommand);
}
String processed = refDetails.substring(0, openIndex);
if (debug) {
System.out.println("Processed: " + processed);
}
String remnants = refDetails.substring(closeIndex + 1,
refDetails.length());
if (debug) System.out.println("Remnants: " + remnants);
boolean addThisCommand = false;
int varIndex = thisCommand.indexOf('$');
while (varIndex >=0) {
if (debug) {
System.out.println("varIndex: " +
varIndex + " " + thisCommand.charAt(varIndex));
}
int endIndex = thisCommand.indexOf(' ', varIndex + 1);
if (endIndex < 0) endIndex = thisCommand.length();
if (debug) System.out.println("endIndex: " + endIndex);
String prefix = thisCommand.substring(0, varIndex);
if (debug) System.out.println("Prefix: " + prefix);
String thisVar = thisCommand.substring(varIndex+1, endIndex);
if (debug) System.out.println("thisVar: " + thisVar);
String suffix;
if (endIndex != thisCommand.length())
suffix = thisCommand.substring(endIndex + 1, thisCommand.length());
else suffix = "";
if (debug) System.out.println("Suffix: " + suffix);
String value = null;
if (oNode != null) {
value = oNode.getAttribute(thisVar);
}
if (value != null) {
if (!value.equals("")) {
addThisCommand = true;
}
}
else value = "";
if (debug) System.out.println("Value: " + value);
thisCommand = prefix + value + suffix;
if (debug) System.out.println("thisCommand: " + thisCommand);
varIndex = thisCommand.indexOf('$');
}
if (addThisCommand) {
noDetails = false;
refDetails = processed + " (" + thisCommand + ")" + remnants;
}
else refDetails = processed + remnants;
if (debug) System.out.println("refDetails: " + refDetails);
openIndex = refDetails.indexOf('%');
}
if (debug) System.out.println(name + " - " + refDetails);
if (showIDs) refDetails = refDetails + " (ID: " + id + ")";
Element cDataEl = DOMUtil.getChildElement("CData", e);
if (cDataEl != null) attrList.add(0, cDataEl);
tableName = null;
if (oNode == null) tableName =
new JLabel(thisName, NO_DATA_BULLET, JLabel.LEFT);
else tableName = new JLabel(thisName, DATA_BULLET, JLabel.LEFT);
Font thisFont = tableName.getFont();
thisFont = new Font(thisFont.getFontName(),
Font.BOLD, 12);
tableName.setFont(thisFont);
if (el.hasAttribute("ShortDesc"))
tableName.setToolTipText(el.getAttribute("ShortDesc"));
else if (el.hasAttribute("Description"))
tableName.setToolTipText(el.getAttribute("Description"));
tableName.setForeground(TEXT_COLOR);
noteButton = new JButton("Notes");
// noteButton.setPreferredSize(new Dimension(85, 17));
noteButton.addActionListener(this);
noteButton.setActionCommand("getNotes");
noteButton.setToolTipText(
"Display or hide the notes associated with this " + name + ".");
noteButton.setForeground(TEXT_COLOR);
imageLabel = null;
if (name.endsWith("Pixels")) {
if (pixelsIDProblem || on == null) {
if (thumb != null && !pixelsIDProblem) {
tableThumb = thumb;
tableImage = img;
imageLabel = new JLabel(new ImageIcon(tableThumb));
imageLabel.setToolTipText("The middle image of these pixels." +
" Click for full sized image.");
imageLabel.addMouseListener(this);
}
if (pixelsIDProblem) JOptionPane.showMessageDialog(this,
"Thumbnails disabled due to multiple pixels with unsupported"
+ " ID naming scheme.",
"MetadataEditor Error", JOptionPane.ERROR_MESSAGE);
}
else {
String thisID = on.getAttribute("ID");
int colonIndex = thisID.indexOf(":");
String pixNumString = thisID.substring(colonIndex + 1);
int pixNum = -1;
try {
pixNum = Integer.parseInt(pixNumString);
int indexNum = pixNum - minPixNum;
tableThumb = thumbs == null ? null : thumbs[indexNum];
tableImage = images == null ? null : images[indexNum];
}
catch (java.lang.NumberFormatException exc) {
//this happens when multiple pixels aren't present
//so we want to show just the one thumb
tableThumb = thumb;
tableImage = img;
}
imageLabel = tableThumb == null ? new JLabel() :
new JLabel(new ImageIcon(tableThumb));
imageLabel.setToolTipText("The middle image of these pixels." +
" Click for full sized image.");
imageLabel.addMouseListener(this);
}
}
DefaultTableModel myTableModel =
new DefaultTableModel(TREE_COLUMNS, 0)
{
public boolean isCellEditable(int row, int col) {
return col >= (editable ? 1 : 2);
}
};
VariableComboEditor vcEdit = new VariableComboEditor(panelsWithID,
addItems, this, internalDefs);
table = new ClickableTable(myTableModel, this, vcEdit);
// table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
table.getColumnModel().getColumn(0).setPreferredWidth(125);
table.getColumnModel().getColumn(1).setPreferredWidth(430);
table.getColumnModel().getColumn(2).setPreferredWidth(70);
table.getColumnModel().getColumn(2).setMaxWidth(70);
table.getColumnModel().getColumn(2).setMinWidth(70);
tHead = table.getTableHeader();
tHead.setResizingAllowed(true);
tHead.setReorderingAllowed(false);
// tHead.setBackground(NotePanel.BACK_COLOR);
// tHead.setOpaque(false);
myTableModel.setRowCount(attrList.size() + refList.size());
String clippedName = name;
if (name.endsWith(")")) {
clippedName = name.substring(0, name.length() - 4);
}
addButton = new JButton("New Table");
// addButton.setPreferredSize(new Dimension(130, 17));
addButton.addActionListener(table);
addButton.setActionCommand("bigAdd");
addButton.setToolTipText("Create a new " + clippedName + " table.");
if (!isTopLevel && tPanel.oNode == null) addButton.setEnabled(false);
if (!editable) addButton.setEnabled(false);
addButton.setForeground(ADD_COLOR);
delButton = new JButton("Delete Table");
// delButton.setPreferredSize(new Dimension(130, 17));
delButton.addActionListener(table);
delButton.setActionCommand("bigRem");
delButton.setToolTipText("Delete this " + clippedName + " table.");
if (oNode == null) delButton.setVisible(false);
if (!editable) delButton.setEnabled(false);
delButton.setForeground(DELETE_COLOR);
noteP = new NotePanel(this);
setNumNotes(noteP.getNumNotes());
FormLayout layout = new FormLayout(
"pref, 10dlu, pref, 10dlu, pref, pref:grow:right, 5dlu, pref",
"pref, 2dlu, pref, pref, 3dlu, pref, 3dlu");
setLayout(layout);
CellConstraints cc = new CellConstraints();
add(tableName, cc.xy(1, 1));
add(noteButton, cc.xy(3, 1, "left, center"));
if (imageLabel != null) {
add(imageLabel, cc.xy(5, 1, "center, top"));
}
add(addButton, cc.xy(6, 1, "right, center"));
add(delButton, cc.xy(8, 1, "right, center"));
add(tHead, cc.xyw(1, 3, 8, "fill, center"));
add(table, cc.xyw(1, 4, 8, "fill, center"));
add(noteP, cc.xyw(1, 6, 8, "fill, center"));
if (oNode == null) {
tHead.setVisible(false);
noteButton.setVisible(false);
table.setVisible(false);
}
if (attrList.size() != 0) {
// update OME-XML attributes table
for (int i=0; i<attrList.size(); i++) {
Element thisEle = null;
if (attrList.get(i) instanceof Element) {
thisEle = (Element) attrList.get(i);
}
if (thisEle != null) {
String attrName = thisEle.getAttribute("XMLName");
if (thisEle.hasAttribute("Name")) {
myTableModel.setValueAt(thisEle.getAttribute("Name"), i, 0);
if (oNode != null) {
if (oNode.getDOMElement().hasAttribute(attrName)) {
myTableModel.setValueAt(oNode.getAttribute(attrName), i, 1);
}
}
}
else if (!thisEle.hasAttribute("Name") &&
thisEle.hasAttribute("XMLName")) {
myTableModel.setValueAt(thisEle.getAttribute("XMLName"), i, 0);
if (oNode != null) {
if (oNode.getDOMElement().hasAttribute(attrName)) {
myTableModel.setValueAt(oNode.getAttribute(attrName), i, 1);
}
}
}
else {
if (e.hasAttribute("Name")) {
myTableModel.setValueAt(e.getAttribute("Name") +
" CharData", i, 0);
}
else {
myTableModel.setValueAt(e.getAttribute("XMLName") +
" CharData", i, 0);
}
if (oNode != null) {
if (DOMUtil.getCharacterData(oNode.getDOMElement()) != null) {
myTableModel.setValueAt(
DOMUtil.getCharacterData(oNode.getDOMElement()), i, 1);
}
}
}
}
}
}
if (refList.size() > 0) {
for (int i=0; i<refList.size(); i++) {
Element thisEle = null;
if (refList.get(i) instanceof Element) {
thisEle = (Element) refList.get(i);
}
if (thisEle != null) {
if (thisEle.hasAttribute("Name")) {
myTableModel.setValueAt(thisEle.getAttribute("Name"),
i + attrList.size(), 0);
}
else if (thisEle.hasAttribute("XMLName")) {
myTableModel.setValueAt(thisEle.getAttribute("XMLName"),
i + attrList.size(), 0);
}
}
}
}
}
/**
* A method to initialize the reference combo boxes to the value
* found in the current file. First sets the Vectors of values
* that are possible for the combobox.
*/
public void setEditor() {
if (table != null) {
//This resets the possible values of the comboboxes.
table.setDefs(panelsWithID, addItems);
TableModel model = table.getModel();
TableColumn refColumn = table.getColumnModel().getColumn(1);
for (int i = 0; i < table.getRowCount(); i++) {
if (i < attrList.size()) {
}
else {
boolean isLocal = false;
String attrName = (String) model.getValueAt(i, 0);
String value = null;
if (oNode != null) value = oNode.getAttribute(attrName);
for (int j = 0; j < panelsWithID.size(); j++) {
TablePanel tp = (TablePanel) panelsWithID.get(j);
if (tp.id != null && value != null) {
if (value.equals(tp.id)) {
isLocal = true;
if (tp.refDetails != null && !tp.refDetails.equals(""))
model.setValueAt(tp.name + " - " + tp.refDetails, i, 1);
else model.setValueAt(tp.name, i, 1);
}
}
}
if (!isLocal && value != null && !value.equals("")) {
model.setValueAt("(External) " + value, i, 1);
}
//makes the initial value non-null to display the buttons
model.setValueAt("foobar", i, 2);
}
}
}
}
/**Set the number of notes displayed on the "Notes" button.*/
public void setNumNotes(int n) {
noteButton.setText("Notes (" + n + ")");
}
/**
* Handles the actions of the "Goto" buttons and also
* the "Notes" button.
*/
public void actionPerformed(ActionEvent e) {
if (e.getSource() instanceof GotoEditor.TableButton) {
GotoEditor.TableButton tb = (GotoEditor.TableButton) e.getSource();
JTable jt = tb.table;
TableModel model = jt.getModel();
Object obj = model.getValueAt(tb.whichRow, 1);
if (obj != null && !obj.toString().equals("")) {
String aName = obj.toString();
TablePanel aPanel = null;
int whichNum = -23;
for (int i = 0; i<panelsWithID.size(); i++) {
aPanel = (TablePanel) panelsWithID.get(i);
if (aName.startsWith(aPanel.name)) whichNum = i;
}
if (whichNum != -23) {
TablePanel tablePan = (TablePanel) panelsWithID.get(whichNum);
TabPanel tp = tablePan.tPanel;
Container anObj = (Container) tp;
while (!(anObj instanceof JScrollPane)) {
anObj = anObj.getParent();
}
JScrollPane jScr = (JScrollPane) anObj;
while (!(anObj instanceof JTabbedPane)) {
anObj = anObj.getParent();
}
JTabbedPane jTabP = (JTabbedPane) anObj;
jTabP.setSelectedComponent(jScr);
Point loc = tablePan.getLocation();
loc.x = 0;
JViewport jView = jScr.getViewport();
if (jView.getExtentSize().getHeight() <
jView.getViewSize().getHeight())
jScr.getViewport().setViewPosition(loc);
}
else {
JOptionPane.showMessageDialog((Frame) getTopLevelAncestor(),
"Since the ID in question refers to something\n" +
"outside of this file, you cannot \"Goto\" it.",
"External Reference Detected", JOptionPane.WARNING_MESSAGE);
}
}
else {
JOptionPane.showMessageDialog((Frame) getTopLevelAncestor(),
"Since the ID in question is currently blank,\n" +
"you cannot \"Goto\" it.",
"Null Reference Detected", JOptionPane.WARNING_MESSAGE);
}
}
else if (e.getActionCommand().equals("getNotes")) {
if (noteP.isVisible()) noteP.setVisible(false);
else noteP.setVisible(true);
noteP.revalidate();
}
}
/**
* Handles the clicking of the icon of Image: Pixels.
* If clicked, this icon will enlarge in a dialog.
*/
public void mouseClicked(MouseEvent e) {
if (e.getSource() instanceof JLabel) {
JOptionPane.showMessageDialog(getTopLevelAncestor(), null,
"(Full Sized) " + name, JOptionPane.PLAIN_MESSAGE,
new ImageIcon(tableImage));
}
}
public void mousePressed(MouseEvent e) {}
public void mouseReleased(MouseEvent e) {}
public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}
/** Call MetadataPane.reRender().*/
public void callReRender() {
reRender();
}
/** Call MetadataPane.stateChanged(boolean change)*/
public void callStateChanged(boolean hasChanged) {
stateChanged(true);
}
/** @return MetadataPane.currentFile*/
public File getCurrentFile() { return currentFile; }
/**
* Checks whether this TablePanel should be editable.
* @return MetadataPane.editable
*/
public boolean isEditable() { return editable; }
}
}