// // ClickableTable.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.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.util.Vector; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JSeparator; import javax.swing.JTable; import javax.swing.JTextArea; import javax.swing.ListSelectionModel; import javax.swing.WindowConstants; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableCellEditor; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableModel; import ome.xml.DOMUtil; import ome.xml.OMEXMLNode; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** * A class that makes tables you can right click to * add or subtract duplicate tables and get information * on the attributes being manipulated. * * <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/ClickableTable.java">Trac</a>, * <a href="http://git.openmicroscopy.org/?p=bioformats.git;a=blob;f=components/legacy/ome-editor/src/loci/ome/editor/ClickableTable.java;hb=HEAD">Gitweb</a></dd></dl> * * @author Christopher Peterson crpeterson2 at wisc.edu */ public class ClickableTable extends JTable implements MouseListener, ActionListener, ListSelectionListener { /** stores the TablePanel this table is associated with. */ protected MetadataPane.TablePanel tp; /** is the current popup menu at any given rightclick. */ protected JPopupMenu jPop; /** the current row being clicked on at any point. */ private int thisRow; /** the name of the attribute in the row being clicked on currently. */ private String attrName; /** * tells at any given point if the TablePanel being added or deleted * is a "duplicate" , e.g. if there is more than one element with its * same tagname on a given level of the node tree. */ private boolean isDuplicate; /** The TableCellRenderers for this ClickableTable. */ protected TableCellRenderer labelR,textR,comboR,gotoR; /** The TableCellEditors for this ClickableTable. */ protected TableCellEditor labelE,textE,comboE,gotoE; /** Whether or not this table should be editable*/ protected boolean editable; // -- ClickableTable Constructors -- /** * Create a new ClickableTable to display OMEXML metadata. * @param model the model of the table of the TablePanel this table is a * part of. * @param tablePanel the TablePanel this table is a part of. * @param vcEdit the VariableComboEditor that should edit all table cells * that are found to be of type "Ref" as specified by Template.xml . */ public ClickableTable(TableModel model, MetadataPane.TablePanel tablePanel, VariableComboEditor vcEdit) { super(model); editable = tablePanel.isEditable(); labelR = new DefaultTableCellRenderer(); comboR = new VariableComboRenderer(); textR = new VariableTextAreaRenderer(); gotoR = new GotoRenderer(); labelE = new VariableTextFieldEditor(tablePanel); vcEdit.refTable = this; comboE = vcEdit; textE = new VariableTextAreaEditor(tablePanel); gotoE = new GotoEditor(tablePanel); addMouseListener(this); //initialize various fields tp = tablePanel; jPop = new JPopupMenu(); thisRow = -1; attrName = null; //setup a selectionlistener on this table so that if any row is selected it //is immediately deselected (work-around //for multiple selection irritations) setSelectionMode(ListSelectionModel.SINGLE_SELECTION); //Ask to be notified of selection changes. ListSelectionModel rowSM = getSelectionModel(); rowSM.addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent e) { //Ignore extra messages. if (e.getValueIsAdjusting()) return; ListSelectionModel lsm = (ListSelectionModel) e.getSource(); if (lsm.isSelectionEmpty()) { //no rows are selected } else { lsm.clearSelection(); //selectedRow is selected } } }); } /**Reset necessary values for the VariableComboEditor.*/ public void setDefs(Vector IDP, Vector AddP) { ((VariableComboEditor)comboE).setDefs(IDP,AddP); } /** * Overide the JTable method to get appropriate tooltips based * on the cell the mouse is currently over. */ public String getToolTipText(MouseEvent e) { String tip = null; java.awt.Point p = e.getPoint(); int rowIndex = rowAtPoint(p); int colIndex = columnAtPoint(p); int realColumnIndex = convertColumnIndexToModel(colIndex); if (realColumnIndex == 0) { TableModel model = getModel(); String name = (String)model.getValueAt(rowIndex,0); Vector v = DOMUtil.getChildElements("OMEAttribute", tp.el); Element thisEle = null; for(int i = 0;i < v.size();i++) { Element tempEle = (Element) v.get(i); if (tempEle.hasAttribute("Name")) { if (tempEle.getAttribute("Name").equals(name)) thisEle = tempEle; } else if (tempEle.getAttribute("XMLName").equals(name)) thisEle = tempEle; } if (thisEle.hasAttribute("ShortDesc")) { tip = thisEle.getAttribute("ShortDesc"); } else if (thisEle.hasAttribute("Description")) { tip = thisEle.getAttribute("Description"); } else tip = super.getToolTipText(e); } // Removed this because it was annoying when trying to change values //else if (realColumnIndex == 1) { // String name = (String)getModel().getValueAt(rowIndex,0); // tip = "The value associated with this " + name + "."; //} else { //another column tip = super.getToolTipText(e); } return tip; } /** * Check Template.xml definitions of a particular cell so that the * cell's type is mapped to the corresponding editor for that type. */ public String getCellType(int row, int column) { TableModel tModel = getModel(); Vector fullList = DOMUtil.getChildElements("OMEAttribute",tp.el); Element templateE = null; for (int i = 0;i<fullList.size();i++) { Element thisE = (Element) fullList.get(i); String nameAttr = thisE.getAttribute("XMLName"); if (thisE.hasAttribute("Name")) nameAttr = thisE.getAttribute("Name"); if (nameAttr.equals((String) tModel.getValueAt(row, 0))) { templateE = thisE; } } String cellType = null; if (templateE.hasAttribute("Type")) { cellType = templateE.getAttribute("Type"); } return cellType; } /** * Overide the JTable method in order to get wierd renderers * based on what row and column we're in. */ public TableCellRenderer getCellRenderer(int row, int column) { if (column == 1) { String cellType = getCellType(row,column); if (cellType != null) { if (cellType.equals("Ref")) { return comboR; } else if (cellType.equals("Desc")) { return textR; } else { return labelR; } } else { return labelR; } } else if (column == 2) { return gotoR; } else return labelR; } /** * Overide the JTable method in order to get wierd editors based * on what row and column we're in. */ public TableCellEditor getCellEditor(int row, int column) { if (column == 1) { String cellType = getCellType(row,column); if (cellType != null) { if (cellType.equals("Ref")) { return comboE; } else if (cellType.equals("Desc")) { return textE; } else { return labelE; } } else { return labelE; } } else if (column == 2) { return gotoE; } else return labelE; } // -- Static ClickableTable API Methods -- /** * tests if the given tagname should be * placed under a CustomAttributesNode. */ public static boolean isInCustom(String tagName) { return MetadataPane.isInCustom(tagName); } /** * tests if this word should have an "a" or an "an" * before it. char c is the first character of the word. */ public static boolean usesAn(char c) { boolean result = false; switch(c) { case 'a': case 'A': case 'e': case 'E': case 'i': case 'I': case 'o': case 'O': case 'h': case 'H': result = true; break; default: result = false; break; } return result; } // -- MouseListener API Methods -- /**Handles the creation of the popup menu on right clicks.*/ public void mousePressed(MouseEvent e) { //test if button 2 or 3 are pressed if (e.getButton() == MouseEvent.BUTTON3 || e.getButton() == MouseEvent.BUTTON2) { //nifty table method, sees which row the pointer is in thisRow = rowAtPoint(e.getPoint()); //given the row, get the appropriate attribute's name attrName = (String) getModel().getValueAt(thisRow,0); //setup the popup menu based on this information jPop = new JPopupMenu("Add/Remove " + attrName + " Attribute:"); JMenuItem infoItem = null; if ( usesAn(attrName.charAt(0)) ) infoItem = new JMenuItem("What is an " + attrName + "?"); else infoItem = new JMenuItem("What is a " + attrName + "?"); //strip away the "(x)" at the end of the tablepanel's name so //it makes sense in the menu, e.g. "Add another Project (2)" //is inaccurate, while "Add another Project" is what we want String realBigName = tp.name; isDuplicate = false; if (realBigName.endsWith(")") ) { isDuplicate = true; realBigName = realBigName.substring(0,realBigName.length()-4); } //setup the various menuitems in the popup menu JMenuItem addItem = new JMenuItem("Add another " + realBigName); if(!editable) addItem.setEnabled(false); JMenuItem bigRemItem = new JMenuItem("Delete this " + realBigName); if(!editable) bigRemItem.setEnabled(false); JMenuItem remItem = new JMenuItem("Delete this " + attrName); if(!editable) remItem.setEnabled(false); infoItem.addActionListener(this); infoItem.setActionCommand("help"); addItem.addActionListener(this); addItem.setActionCommand("bigAdd"); bigRemItem.addActionListener(this); bigRemItem.setActionCommand("bigRem"); remItem.addActionListener(this); remItem.setActionCommand("delete"); // addItem.setForeground(new Color(0,100,0)); // bigRemItem.setForeground(new Color(100,0,0)); // remItem.setForeground(new Color(100,0,0)); //add the menuitems to the popup menu, add logical separators jPop.add(addItem); jPop.add(bigRemItem); JSeparator sep = new JSeparator(); jPop.add(sep); jPop.add(remItem); JSeparator sep2 = new JSeparator(); jPop.add(sep2); jPop.add(infoItem); jPop.show(this, e.getX(), e.getY()); } } //abstract methods we must override but have no use for public void mouseReleased(MouseEvent e) {} public void mouseClicked(MouseEvent e) {} public void mouseEntered(MouseEvent e) {} public void mouseExited(MouseEvent e) {} // -- ActionLister API Methods -- /** Handles the actions caused by selection in the popup menu. */ public void actionPerformed(ActionEvent e) { String cmd = e.getActionCommand(); //create a HelpFrame if user requests help on an attribute if ("help".equals(cmd)) { HelpFrame helpWin = new HelpFrame(); } //handle deleting of a single attribute's value in the table if ("delete".equals(cmd)) { //get a list of all attributes in this TablePanel Vector attrVector = DOMUtil.getChildElements("OMEAttribute", tp.el); Element thisAttr = null; //test if attrName is a "Name" or "XMLName" attribute, find the element //that matches attrName for (int i = 0;i<attrVector.size();i++) { Element temp = (Element) attrVector.get(i); if (temp.hasAttribute("Name")) { if (attrName.equals(temp.getAttribute("Name")) ) thisAttr = temp; } else if (temp.hasAttribute("XMLName") && !temp.hasAttribute("Name") ) { if (attrName.equals(temp.getAttribute("XMLName")) ) thisAttr = temp; } } tp.callStateChanged(true); //set nodetree to reflect a blank attribute here, also set table blank tp.oNode.getDOMElement().removeAttribute( thisAttr.getAttribute("XMLName")); getModel().setValueAt("", thisRow, 1); } //this signifies that the user wants to add another "clone" TablePanel if ("bigAdd".equals(cmd)) { if (tp.oNode == null) { if (tp.isTopLevel) { tp.tPanel.oNode = MetadataPane.makeNode( tp.tPanel.el.getAttribute("XMLName"), tp.tPanel.ome); tp.oNode = tp.tPanel.oNode; } else { if (tp.tPanel.oNode != null) { tp.oNode = MetadataPane.makeNode( tp.el.getAttribute("XMLName"), tp.tPanel.oNode); } } tp.callReRender(); } else { //get the tagname of the element associated with this tablepanel String thisTagName =tp.oNode.getDOMElement().getTagName(); //test if the tablepanel in question is actually a tab, e.g. the only //ancestor nodes are CustomAttributesNode and/or OMENode if (tp.isTopLevel) { //test if we need to deal with CustomAttributesNodes using the //isInCustom(String tagName) static method MetadataPane.makeNode(thisTagName,tp.tPanel.ome); //tell the tablepanel to tell the MetadataPane to redo its GUI based //on the new node tree structure tp.callReRender(); } //if tablepanel doesn't represent a "top-level" element else { //test if we need to deal with CustomAttributesNodes MetadataPane.makeNode(thisTagName,tp.tPanel.oNode); //tell the tablepanel to tell the MetadataPane to redo its GUI based //on the new node tree structure tp.callReRender(); } } tp.callStateChanged(true); } //signifies user wishes to delete an entire tablepanel. //N.B. : if there is only one instance of the tablepanel in question, //it will be deleted then recreated blank in order to comply with the //template if ("bigRem".equals(cmd)) { //test if we're dealing with a "top-level" element if (tp.isTopLevel) { String thisTagName =tp.oNode.getDOMElement().getTagName(); Element parentEle = null; if (!isInCustom(thisTagName)) { parentEle = tp.tPanel.ome.getDOMElement(); //remove the node in question from its parent parentEle.removeChild((Node) tp.oNode.getDOMElement()); } else { OMEXMLNode realParent = tp.tPanel.ome.getChildNode("CustomAttributes"); parentEle = realParent.getDOMElement(); //remove the node in question from its (CustomAttributes) parent parentEle.removeChild((Node) tp.oNode.getDOMElement()); NodeList caChildren = parentEle.getChildNodes(); if (caChildren != null) { if (caChildren.getLength() == 0) { tp.tPanel.ome.getDOMElement().removeChild( (Node) parentEle); } } else tp.tPanel.oNode.getDOMElement().removeChild( (Node) parentEle); } //tell the tablepanel to tell the MetadataPane to redo its GUI based on //the new node tree structure tp.callReRender(); } //if not a "top-level" element, do this else { String thisTagName =tp.oNode.getDOMElement().getTagName(); if (!isInCustom(thisTagName)) { Element parentEle = tp.tPanel.oNode.getDOMElement(); parentEle.removeChild((Node) tp.oNode.getDOMElement()); } else { OMEXMLNode realParent = tp.tPanel.oNode.getChildNode("CustomAttributes"); Element parentEle = realParent.getDOMElement(); parentEle.removeChild((Node) tp.oNode.getDOMElement()); NodeList caChildren = parentEle.getChildNodes(); if (caChildren != null) { if (caChildren.getLength() == 0) { tp.tPanel.oNode.getDOMElement().removeChild( (Node) parentEle); } } else tp.tPanel.oNode.getDOMElement().removeChild( (Node) parentEle); } //tell the tablepanel to tell the MetadataPane to redo its GUI based on //the new node tree structure tp.callReRender(); } tp.callStateChanged(true); } } // -- Helper Classes -- /** Defines a little nifty window for displaying help.*/ public class HelpFrame extends JFrame { //the only constructor public HelpFrame() { //set up the frame itself super("Help! - " + tp.name); setLocation(200,200); setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); //make a content panel to display stuff on, set its size JPanel contentPanel = new JPanel(); Dimension dim = new Dimension(300,125); contentPanel.setPreferredSize(dim); contentPanel.setLayout(new BorderLayout()); setContentPane(contentPanel); contentPanel.setBackground(new Color(0,0,50)); //create a label corresponding to the attribute in question JLabel titleLabel = new JLabel(" " + attrName + ":"); Font thisFont = titleLabel.getFont(); Font newFont = new Font(thisFont.getFontName(),Font.BOLD,18); titleLabel.setFont(newFont); contentPanel.add(titleLabel, BorderLayout.NORTH); titleLabel.setForeground(new Color(255,255,255)); //set default help text String desc = " No description available for " + attrName + "."; //cruise the template's node tree to get the appropriate //OMEAttribute's "Description" attribute Vector attrVector = DOMUtil.getChildElements("OMEAttribute", tp.el); Element thisAttr = null; for (int i = 0;i<attrVector.size();i++) { Element temp = (Element) attrVector.get(i); if (temp.hasAttribute("Name")) { if (attrName.equals(temp.getAttribute("Name")) ) thisAttr = temp; } else if (temp.hasAttribute("XMLName") && ! temp.hasAttribute("Name") ) { if (attrName.equals(temp.getAttribute("XMLName")) ) thisAttr = temp; } } if (thisAttr != null && thisAttr.hasAttribute("Description")) desc = " " + thisAttr.getAttribute("Description"); //make a textarea to hold the description found JTextArea descArea = new JTextArea(desc); descArea.setEditable(false); descArea.setLineWrap(true); descArea.setWrapStyleWord(true); JScrollPane jScr = new JScrollPane(descArea); contentPanel.add(jScr, BorderLayout.CENTER); //make the frame the right size and visible pack(); setVisible(true); } } }