// // MetadataEditor.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.CardLayout; import java.awt.Cursor; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.InputEvent; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.KeyEvent; import java.io.File; import javax.swing.JCheckBoxMenuItem; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JSeparator; import javax.swing.KeyStroke; import javax.swing.UIManager; import javax.swing.border.EmptyBorder; import javax.swing.filechooser.FileFilter; import loci.formats.gui.BufferedImageReader; import loci.formats.gui.ExtensionFileFilter; import loci.formats.gui.GUITools; import org.openmicroscopy.xml.OMENode; import org.w3c.dom.Element; /** * An user-friendly application for displaying and editing OME-XML metadata. * * <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/MetadataEditor.java">Trac</a>, * <a href="http://git.openmicroscopy.org/?p=bioformats.git;a=blob;f=components/legacy/ome-editor/src/loci/ome/editor/MetadataEditor.java;hb=HEAD">Gitweb</a></dd></dl> * * @author Christopher Peterson crpeterson2 at wisc.edu */ public class MetadataEditor extends JFrame implements ActionListener, ItemListener, Runnable { // -- Constants -- /** URL of OME Metadata Editor web page. */ public static final String URL_OME_METADATA_EDITOR = "http://www.loci.wisc.edu/software/ome-metadata-editor"; /** Key mask for use with keyboard shortcuts on this operating system. */ public static final int MENU_MASK = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); // -- Fields -- /**The file chooser used to save and open files.*/ protected JFileChooser opener, saver; /**Format filters for saver JFileChooser.*/ protected ExtensionFileFilter tiffFilter, omeFilter; /**The MetadataPane used to display/edit OMEXML content.*/ protected MetadataPane metadata; /**Holds the current file being displayed.*/ protected File currentFile; /**The "Tabs" menu.*/ protected JMenu tabsMenu; /**Signifies whether we're opening(true) or saving(false) a file.*/ protected boolean opening; /**Holds the xml viewer that displays xml data in a JTree.*/ protected loci.ome.viewer.MetadataPane mdp; /**The File>New menu item.*/ protected JMenuItem fileNew; /**The File>Save menu item.*/ protected JMenuItem fileSave; /**The NotePane that displays a comprehensive list of all notes.*/ protected NotePane noteP; /** * The WiscScan emulator that mimics the GUI of the WiscScan * program for ease of use by our in-house biologists. */ protected WiscScanPane scanP; /**The checkboxes that switch between the four views.*/ protected JCheckBoxMenuItem advView, noteView, normView, scanView, showID; // -- Constructors -- public MetadataEditor() { this((String[]) null); } /** Create a default editor window with save function and editing enabled.*/ public MetadataEditor(String[] args) { this(args, (OMENode) null, (String) null, true, true); } /** * Create an editor window with specified save and editing policies. * @param args an array of strings the first entry of which should be a * filename, otherwise, send a (String[]) null as this parameter. * @param ome An OMENode xml root to be launched if a filename is not * appropriate, for instance when in the LociDataBrowser we have a * FilePattern if using a FileStitcher. Thus, send ome instead. Note * that this is a temporary fix. If your file is OME-Tiff there's going * to be a problem if saving is enabled and you try to save to it, since * we're circumventing the code that flags TIFF files. * @param title Sets the title of the editor window, which is done by * default if a file URL is given in args, but otherwise should be set * using this String parameter. * @param addSave whether or not saving should be enabled * @param editable whether or not users should be able to edit the xml */ public MetadataEditor(String[] args, OMENode ome, String title, boolean addSave, boolean editable) { super("OME Metadata Editor"); if (title != null) setTitle(title); try { String os = System.getProperty("os.name"); String laf = os != null && os.indexOf("Windows") >= 0 ? "com.sun.java.swing.plaf.windows.WindowsLookAndFeel" : "javax.swing.plaf.metal.MetalLookAndFeel"; UIManager.setLookAndFeel(laf); } catch (Exception exc) { System.err.println("Sorry, but we could not find the look and feel JAR."); } //initialize fields currentFile = null; opening = true; //give the Template.xml file to the parser to feed on TemplateParser tp = new TemplateParser("Template.xml"); mdp = new loci.ome.viewer.MetadataPane(); noteP = new NotePane(); scanP = new WiscScanPane(); scanP.setEditable(editable); //create a MetadataPane, where most everything happens if (args != null && args.length > 0) { File file = null; try { file = new File(args[0]); } catch (Exception exc) { System.out.println("Error occured: You suck."); } currentFile = file; metadata = new MetadataPane(file, addSave, editable); setTitle("OME Metadata Editor - " + file); } else metadata = new MetadataPane((File) null, addSave, editable); if (ome != null) metadata.setOMEXML(ome); metadata.setVisible(true); mdp.setVisible(false); noteP.setVisible(false); scanP.setVisible(false); JPanel contentPanel = new JPanel(); contentPanel.setLayout(new CardLayout()); contentPanel.setBorder((EmptyBorder) null); contentPanel.add("editor", metadata); contentPanel.add("viewer", mdp); contentPanel.add("notes", noteP); contentPanel.add("scan", scanP); setContentPane(contentPanel); //setup the menus on this frame JMenuBar menubar = new JMenuBar(); setJMenuBar(menubar); JMenu file = new JMenu("File"); menubar.add(file); file.setMnemonic('f'); fileNew = new JMenuItem("New..."); file.add(fileNew); fileNew.setActionCommand("new"); fileNew.addActionListener(this); fileNew.setMnemonic('n'); fileNew.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, MENU_MASK)); JMenuItem fileOpen = new JMenuItem("Open"); file.add(fileOpen); fileOpen.setActionCommand("open"); fileOpen.addActionListener(this); fileOpen.setMnemonic('o'); fileOpen.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, MENU_MASK)); JSeparator jSep = new JSeparator(); file.add(jSep); fileSave = new JMenuItem("Save"); file.add(fileSave); fileSave.setActionCommand("save"); fileSave.addActionListener(this); fileSave.setMnemonic('s'); fileSave.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, MENU_MASK)); fileSave.setEnabled(addSave); JMenuItem fileSaveComp = new JMenuItem("Save to Companion"); file.add(fileSaveComp); fileSaveComp.setActionCommand("saveComp"); fileSaveComp.addActionListener(this); fileSaveComp.setMnemonic('c'); fileSaveComp.setAccelerator( KeyStroke.getKeyStroke(KeyEvent.VK_C, MENU_MASK)); fileSaveComp.setEnabled(addSave); JMenuItem fileSaveAs = new JMenuItem("Save As..."); file.add(fileSaveAs); fileSaveAs.setActionCommand("saveAs"); fileSaveAs.addActionListener(this); fileSaveAs.setMnemonic('s'); fileSaveAs.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, KeyEvent.CTRL_MASK|KeyEvent.SHIFT_MASK)); fileSaveAs.setEnabled(addSave); jSep = new JSeparator(); file.add(jSep); JMenuItem fileExit = new JMenuItem("Exit"); file.add(fileExit); fileExit.setActionCommand("exit"); fileExit.addActionListener(this); fileExit.setMnemonic('x'); fileExit.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, MENU_MASK)); //setup the tab menu to reflect the top-level tab names gathered by //TemplateParser from the template in Template.xml tabsMenu = new JMenu("Tabs"); tabsMenu.setMnemonic('b'); menubar.add(tabsMenu); Element[] tabs = tp.getTabs(); String[] tabNames = new String[tabs.length]; for (int i = 0; i<tabs.length; i++) { Element e = tabs[i]; tabNames[i] = MetadataPane.getTreePathName(e); } //call the method that changes the names in the Tabs menu changeTabMenu(tabNames); JMenu toolsMenu = new JMenu("Tools"); toolsMenu.setMnemonic('t'); menubar.add(toolsMenu); normView = new JCheckBoxMenuItem("Normal View"); normView.setSelected(true); toolsMenu.add(normView); normView.addItemListener(this); normView.setMnemonic('r'); normView.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, MENU_MASK)); scanView = new JCheckBoxMenuItem("WiscScan View"); scanView.setSelected(false); toolsMenu.add(scanView); scanView.addItemListener(this); scanView.setMnemonic('w'); scanView.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W, MENU_MASK)); noteView = new JCheckBoxMenuItem("Note List View"); noteView.setSelected(false); toolsMenu.add(noteView); noteView.addItemListener(this); noteView.setMnemonic('l'); noteView.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_L, MENU_MASK)); advView = new JCheckBoxMenuItem("XML View"); advView.setSelected(false); toolsMenu.add(advView); advView.addItemListener(this); advView.setMnemonic('v'); advView.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V, MENU_MASK)); JSeparator sep = new JSeparator(); toolsMenu.add(sep); JMenuItem exportItem = new JMenuItem("Export Notes"); exportItem.setSelected(false); toolsMenu.add(exportItem); exportItem.addActionListener(this); exportItem.setActionCommand("export"); exportItem.setMnemonic('x'); exportItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, MENU_MASK)); JMenuItem mergeItem = new JMenuItem("Merge Companion File"); mergeItem.setSelected(false); toolsMenu.add(mergeItem); mergeItem.addActionListener(this); mergeItem.setActionCommand("merge"); mergeItem.setMnemonic('m'); mergeItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, MENU_MASK)); JMenu options = new JMenu("Options"); options.setMnemonic('o'); menubar.add(options); showID = new JCheckBoxMenuItem("Show IDs"); showID.setSelected(false); options.add(showID); showID.addItemListener(this); showID.setMnemonic('i'); showID.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, MENU_MASK)); JMenu help = new JMenu("Help"); help.setMnemonic('h'); menubar.add(help); JMenuItem helpAbout = new JMenuItem("About"); help.add(helpAbout); helpAbout.setActionCommand("about"); helpAbout.addActionListener(this); helpAbout.setMnemonic('a'); helpAbout.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, MENU_MASK)); //make a filechooser to open and save our precious files tiffFilter = new ExtensionFileFilter( new String[] {"tif", "tiff"}, "Tagged Image File Format"); omeFilter = new ExtensionFileFilter("ome", "OME-XML"); ExtensionFileFilter allFilter = new ExtensionFileFilter( new String[] {"tif", "tiff", "ome"}, "All supported file formats"); ExtensionFileFilter[] filters = new ExtensionFileFilter[] {tiffFilter, omeFilter}; saver = GUITools.buildFileChooser(filters); saver.setCurrentDirectory(new File(System.getProperty("user.dir"))); if (metadata.reader == null) metadata.reader = new BufferedImageReader(); opener = GUITools.buildFileChooser(metadata.reader); opener.setCurrentDirectory(new File(System.getProperty("user.dir"))); //make WiscScan view the default //scanView.setSelected(true); //useful frame method that handles closing of window setDefaultCloseOperation(DISPOSE_ON_CLOSE); //put frame in the right place, with the right size, and make visible setLocation(100, 100); pack(); setVisible(true); } // -- MetadataEditor API methods -- /** Sets the current file being displayed to this file. */ protected void setCurrentFile(File aFile) { currentFile = aFile; boolean allowSave = !metadata.testThirdParty(currentFile); fileSave.setEnabled(allowSave); for (int i=0; i<metadata.tabPanelList.size(); i++) { MetadataPane.TabPanel tp = (MetadataPane.TabPanel) metadata.tabPanelList.get(i); tp.saveButton.setEnabled(allowSave); } } /** Opens a file, sets the title of the frame to reflect the current file. */ public void openFile(File file) { metadata.setOMEXML(file); mdp.setOMEXML(file); scanP.setOMEXML(metadata.getRoot()); if (noteView.getState()) noteP.setPanels(metadata.panelList); setTitle("OME Metadata Editor - " + file); } /** Saves to a file, sets title of frame to reflect the current file. */ public void saveFile(File file) { metadata.saveFile(file); } /** Saves to a companion file, same path with .meta extenstion, pure ome. */ public void saveCompanionFile(File file) { metadata.saveCompanionFile(file); } public void saveTiffFile(File file) { metadata.saveTiffFile(file); } public void saveTiffFile(File file, String outPath) { metadata.saveTiffFile(file, outPath); } /** * Given an array of Strings of appropriate tab names, * this method sets up the tab menu accordingly. */ public void changeTabMenu(String[] tabs) { tabsMenu.removeAll(); for (int i=0; i<tabs.length; i++) { String thisName = tabs[i]; JMenuItem thisTab = new JMenuItem(thisName); tabsMenu.add(thisTab); //set up shortcut keys if tabs menu has less than 11 items if ((i+1) < 11) { thisTab.setAccelerator(KeyStroke.getKeyStroke( MetadataPane.getKey(i+1), InputEvent.ALT_MASK)); } Integer aInt = new Integer(i); thisTab.setActionCommand("tabChange" + aInt.toString()); thisTab.addActionListener(this); } } // -- ActionListener API methods -- /** Handles menu commands. */ public void actionPerformed(ActionEvent e) { String cmd = e.getActionCommand(); if ("new".equals(cmd)) { if (metadata.getState()) { Object[] options = {"Yes, do it!", "No thanks."}; int n = JOptionPane.showOptionDialog(this, "Are you sure you want to create\n" + "a new file without saving your\n" + "changes to the current file?", "Current File Not Saved", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, //don't use a custom Icon options, //the titles of buttons options[0]); //default button title if (n == JOptionPane.YES_OPTION) { setTitle("OME Metadata Editor"); currentFile = null; metadata.setupTabs(); } } else { setTitle("OME Metadata Editor"); currentFile = null; metadata.setupTabs(); } } else if ("open".equals(cmd)) { if (metadata.getState()) { Object[] options = {"Yes, do it!", "No thanks."}; int n = JOptionPane.showOptionDialog(this, "Are you sure you want to open\n" + "a new file without saving your\n" + "changes to the current file?", "Current File Not Saved", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, //don't use a custom Icon options, //the titles of buttons options[0]); //default button title if (n == JOptionPane.YES_OPTION) { opening = true; int rval = opener.showOpenDialog(this); if (rval == JFileChooser.APPROVE_OPTION) { new Thread(this, "MetadataEditor-Opener").start(); } } } else { opening = true; int rval = opener.showOpenDialog(this); if (rval == JFileChooser.APPROVE_OPTION) { new Thread(this, "MetadataEditor-Opener").start(); } } } else if ("saveAs".equals(cmd) || ("save".equals(cmd) && currentFile == null)) { opening = false; int rval = saver.showSaveDialog(this); if (rval == JFileChooser.APPROVE_OPTION) { new Thread(this, "MetadataEditor-Saver").start(); metadata.stateChanged(false); } } else if ("save".equals(cmd) && currentFile != null) { saveFile(currentFile); metadata.stateChanged(false); } else if ("saveComp".equals(cmd) && currentFile != null) { saveCompanionFile(currentFile); metadata.stateChanged(false); } else if ("merge".equals(cmd)) { metadata.merge(); } else if ("exit".equals(cmd)) { if (metadata.getState()) { Object[] options = {"Yes, exit!", "No thanks."}; int n = JOptionPane.showOptionDialog(this, "Are you sure you want to exit without\n" + "saving your changes to the current file?", "Current File Not Saved", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, //don't use a custom Icon options, //the titles of buttons options[0]); //default button title if (n == JOptionPane.YES_OPTION) { System.exit(0); } } else { System.exit(0); } } else if ("about".equals(cmd)) { JOptionPane.showMessageDialog(this, "OME Metadata Editor\n" + "Revision @vcs.revision@, built @date@\n\n" + "The OME Metadata Editor is LOCI software written by\n" + "Christopher Peterson.\n" + URL_OME_METADATA_EDITOR, "OME Metadata Editor", JOptionPane.INFORMATION_MESSAGE); } else if (cmd.startsWith("tabChange")) { metadata.tabChange(Integer.parseInt(cmd.substring(9))); } else if ("export".equals(cmd)) { noteP.tPanels = metadata.panelList; noteP.exportNotes(); } } /**Handles the checkbox menuitems that change the view.*/ public void itemStateChanged(ItemEvent e) { if (e.getStateChange() == ItemEvent.SELECTED && (JCheckBoxMenuItem) e.getItem() == advView) { noteView.setState(false); normView.setState(false); scanView.setState(false); metadata.setVisible(false); noteP.setVisible(false); scanP.setVisible(false); tabsMenu.setEnabled(false); fileNew.setEnabled(false); mdp.setOMEXML(metadata.getRoot()); mdp.setVisible(true); } else if (e.getStateChange() == ItemEvent.SELECTED && (JCheckBoxMenuItem) e.getItem() == noteView) { advView.setState(false); normView.setState(false); scanView.setState(false); metadata.setVisible(false); mdp.setVisible(false); scanP.setVisible(false); tabsMenu.setEnabled(false); fileNew.setEnabled(false); noteP.setPanels(metadata.panelList); noteP.setVisible(true); } else if (e.getStateChange() == ItemEvent.SELECTED && (JCheckBoxMenuItem) e.getItem() == scanView) { noteView.setState(false); advView.setState(false); normView.setState(false); metadata.setVisible(false); mdp.setVisible(false); tabsMenu.setEnabled(false); fileNew.setEnabled(false); scanP.setOMEXML(metadata.getRoot()); scanP.setVisible(true); } else if (e.getStateChange() == ItemEvent.SELECTED && (JCheckBoxMenuItem) e.getItem() == normView) { advView.setState(false); noteView.setState(false); scanView.setState(false); noteP.setVisible(false); scanP.setVisible(false); mdp.setVisible(false); tabsMenu.setEnabled(true); fileNew.setEnabled(true); metadata.reRender(); metadata.setVisible(true); } else if (e.getStateChange() == ItemEvent.SELECTED && (JCheckBoxMenuItem) e.getItem() == showID) { metadata.showIDs = true; metadata.reRender(); } else if (e.getStateChange() == ItemEvent.DESELECTED && (JCheckBoxMenuItem) e.getItem() == showID) { metadata.showIDs = false; metadata.reRender(); } else { if (!advView.getState() && !noteView.getState() && !scanView.getState()) { normView.setState(true); } else { noteP.setVisible(false); mdp.setVisible(false); scanP.setVisible(false); tabsMenu.setEnabled(true); fileNew.setEnabled(true); metadata.reRender(); metadata.setVisible(true); } } } // -- Runnable API methods -- /** Opens a file in a separate thread. */ public void run() { wait(true); if (opening) { currentFile = opener.getSelectedFile(); openFile(currentFile); } else { File outFile = saver.getSelectedFile(); FileFilter filter = saver.getFileFilter(); if (filter.equals((FileFilter) omeFilter)) { if (outFile.getPath().endsWith(".ome")) { currentFile = outFile; saveFile(outFile); } else { outFile = new File(outFile.getPath() + ".ome"); currentFile = outFile; saveFile(outFile); } } else if (filter.equals((FileFilter) tiffFilter)) { if (outFile.getPath().endsWith(".tif") || outFile.getPath().endsWith(".tiff")) { saveTiffFile(currentFile, outFile.getPath()); currentFile = outFile; } else { outFile = new File(outFile.getPath() + ".tif"); saveTiffFile(currentFile, outFile.getPath()); currentFile = outFile; } } else { String path = outFile.getPath(); if (path.endsWith("ome")) saveFile(currentFile); else if (path.endsWith("tif") || path.endsWith("tiff")) { saveTiffFile(currentFile, path); } else { System.out.println("We could not identify which format you wanted, " + "so the file was not saved."); } } } wait(false); } // -- Helper methods -- /** Toggles wait cursor. */ protected void wait(boolean wait) { setCursor(wait ? Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR) : null); } // -- Main method -- /**Test method for debug uses, or simply to bring up an editor window * from the console or whatever. */ public static void main(String[] args) { new MetadataEditor(args); } }