/* * Copyright (c) 2009 The Jackson Laboratory * * This software was developed by Gary Churchill's Lab at The Jackson * Laboratory (see http://research.jax.org/faculty/churchill). * * This is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this software. If not, see <http://www.gnu.org/licenses/>. */ package org.jax.qtl.project.gui; import java.awt.Component; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.net.URL; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JPopupMenu; import javax.swing.JTree; import javax.swing.SwingUtilities; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; import org.jax.qtl.QTL; import org.jax.qtl.cross.Cross; import org.jax.qtl.cross.QtlBasket; import org.jax.qtl.cross.RunJittermapAction; import org.jax.qtl.cross.gui.EditQtlBasketAction; import org.jax.qtl.cross.gui.ShowCrossSummaryAction; import org.jax.qtl.fit.FitQtlResult; import org.jax.qtl.fit.gui.FitQtlAction; import org.jax.qtl.fit.gui.ShowFitQtlResultsAction; import org.jax.qtl.project.QtlDataModel; import org.jax.qtl.project.QtlDataModelListener; import org.jax.qtl.project.QtlProject; import org.jax.qtl.project.QtlProjectManager; import org.jax.qtl.scan.ScanOneResult; import org.jax.qtl.scan.ScanTwoResult; import org.jax.qtl.scan.gui.PlotScanOneResultAction; import org.jax.qtl.scan.gui.PlotScanTwoResultAction; import org.jax.qtl.scan.gui.ScanOneSummaryAction; import org.jax.qtl.scan.gui.ScanTwoSummaryAction; import org.jax.r.jriutilities.RInterface; import org.jax.r.jriutilities.RInterfaceFactory; import org.jax.util.gui.Iconifiable; import org.jax.util.gui.SafeDeleteAction; import org.jax.util.gui.SwingTreeUtilities; import org.jax.util.project.Project; import org.jax.util.project.gui.ProjectTree; /** * For displaying J/qtl project data as a {@link JTree} * @author <A HREF="mailto:keith.sheppard@jax.org">Keith Sheppard</A> */ public class QtlProjectTree extends ProjectTree { /** * every {@link java.io.Serializable} is supposed to have one of these */ private static final long serialVersionUID = 7242325371548165892L; /** * our logger */ private static final Logger LOG = Logger.getLogger( QtlProjectTree.class.getName()); /** * listens to the data model for changes */ private QtlDataModelListener dataModelListener = new QtlDataModelListener() { public void crossAdded(QtlDataModel source, Cross cross) { this.changeOccured(); } public void crossRemoved(QtlDataModel source, Cross cross) { this.changeOccured(); } private void changeOccured() { SwingUtilities.invokeLater(new Runnable() { public void run() { QtlProjectTree.this.refreshCrossNodes(); } }); } }; /** * @see #getSelectedCross() */ private volatile Cross selectedCross = null; /** * @see #getSelectedScanOneResult() */ private volatile ScanOneResult selectedScanOneResult = null; /** * @see #getSelectedScanTwoResult() */ private volatile ScanTwoResult selectedScanTwoResult = null; /** * @see #getSelectedQtlBasket() */ private volatile QtlBasket selectedQtlBasket; /** * Constructor */ public QtlProjectTree() { this.getSelectionModel().setSelectionMode( TreeSelectionModel.SINGLE_TREE_SELECTION); this.addTreeSelectionListener(new TreeSelectionListener() { public void valueChanged(TreeSelectionEvent treeSelectionEvent) { QtlProjectTree.this.treeSelectionChanged(treeSelectionEvent); } }); } /** * For responding to a tree selection event * @param treeSelectionEvent * the selection event to respond to */ private void treeSelectionChanged(TreeSelectionEvent treeSelectionEvent) { TreePath selectionPath = treeSelectionEvent.getNewLeadSelectionPath(); if(selectionPath == null) { this.setSelectedCross(null); } else { // search for the selected cross going toward the root Object[] treePath = selectionPath.getPath(); boolean foundSelectedCross = false; boolean foundSelectedScanOneResult = false; boolean foundSelectedScanTwoResult = false; boolean foundSelectedQtlBasketResult = false; for(int i = treePath.length - 1; i >= 0; i--) { Object currSelectedNode = treePath[i]; if(currSelectedNode instanceof CrossTreeNode) { if(!foundSelectedCross) { CrossTreeNode currCrossTreeNode = (CrossTreeNode)currSelectedNode; this.setSelectedCross(currCrossTreeNode.getCross()); foundSelectedCross = true; } } else if(currSelectedNode instanceof ScanOneTreeNode) { if(!foundSelectedScanOneResult) { ScanOneTreeNode currScanOneTreeNode = (ScanOneTreeNode)currSelectedNode; this.setSelectedScanOneResult(currScanOneTreeNode.getScanOneResult()); foundSelectedScanOneResult = true; } } else if(currSelectedNode instanceof ScanTwoTreeNode) { if(!foundSelectedScanTwoResult) { ScanTwoTreeNode currScanTwoTreeNode = (ScanTwoTreeNode)currSelectedNode; this.setSelectedScanTwoResult(currScanTwoTreeNode.getScanTwoResult()); foundSelectedScanTwoResult = true; } } else if(currSelectedNode instanceof QtlBasketNode) { if(!foundSelectedQtlBasketResult) { QtlBasketNode currQtlBasketNode = (QtlBasketNode)currSelectedNode; this.setSelectedQtlBasket(currQtlBasketNode.getQtlBasket()); foundSelectedQtlBasketResult = true; } } } // set to null if we couldn't find what we were looking for if(!foundSelectedCross) { this.setSelectedCross(null); } if(!foundSelectedScanOneResult) { this.setSelectedScanOneResult(null); } if(!foundSelectedScanTwoResult) { this.setSelectedScanTwoResult(null); } if(!foundSelectedQtlBasketResult) { this.setSelectedQtlBasket(null); } } } /** * Setter for the selected QTL basket * @param selectedQtlBasket * the selected QTL basket */ private void setSelectedQtlBasket(QtlBasket selectedQtlBasket) { if(LOG.isLoggable(Level.FINE)) { LOG.fine("selected QTL basket set to: " + selectedQtlBasket); } this.selectedQtlBasket = selectedQtlBasket; } /** * Getter for the selected QTL basket * @return the selectedQtlBasket */ public QtlBasket getSelectedQtlBasket() { return this.selectedQtlBasket; } /** * Setter for the selected scanone result * @param selectedScanOneResult * the result to set */ private void setSelectedScanOneResult(ScanOneResult selectedScanOneResult) { if(LOG.isLoggable(Level.FINE)) { LOG.fine("selected scan one result set to: " + selectedScanOneResult); } this.selectedScanOneResult = selectedScanOneResult; } /** * Getter for the selected scanone result * @return * the selected scan one result */ public ScanOneResult getSelectedScanOneResult() { return this.selectedScanOneResult; } /** * Setter for the selected scantwo result * @param selectedScanTwoResult * the selectedScanTwoResult to set */ public void setSelectedScanTwoResult(ScanTwoResult selectedScanTwoResult) { if(LOG.isLoggable(Level.FINE)) { LOG.fine("selected scan two result set to: " + selectedScanTwoResult); } this.selectedScanTwoResult = selectedScanTwoResult; } /** * Getter for the selected scantwo result * @return * the selected scantwo result */ public ScanTwoResult getSelectedScanTwoResult() { return this.selectedScanTwoResult; } /** * Setter for the selected cross * @param selectedCross * the new value for selected cross */ private void setSelectedCross(Cross selectedCross) { if(LOG.isLoggable(Level.FINE)) { LOG.fine("selected cross set to: " + selectedCross); } this.selectedCross = selectedCross; } /** * Getter for the cross that the user has selected * @return * the cross or null if no cross is selected */ public Cross getSelectedCross() { return this.selectedCross; } /** * {@inheritDoc} */ @Override public QtlProject getActiveProject() { return (QtlProject)super.getActiveProject(); } /** * Set the currently active project * @param activeProject * the new active project */ @Override public void setActiveProject(Project activeProject) { QtlProject qtlActiveProject = (QtlProject)activeProject; { QtlProject oldActiveProject = this.getActiveProject(); if(oldActiveProject != null) { oldActiveProject.getDataModel().removeQtlDataModelListener( this.dataModelListener); } } if(qtlActiveProject != null) { qtlActiveProject.getDataModel().addQtlDataModelListener( this.dataModelListener); } super.setActiveProject(activeProject); } /** * {@inheritDoc} */ @Override public QtlProjectManager getProjectManager() { return (QtlProjectManager)super.getProjectManager(); } /** * refresh the project tree */ @Override protected void refreshProjectTree() { QtlProject activeProject = this.getProjectManager().getActiveProject(); final boolean activeProjectIsRoot; { final Object rootObject = this.getModel().getRoot(); if(rootObject instanceof ProjectTreeNode) { final ProjectTreeNode root = (ProjectTreeNode)rootObject; activeProjectIsRoot = root.getProject() == activeProject; } else { activeProjectIsRoot = false; } } if(!activeProjectIsRoot) { this.getModel().setRoot(new ProjectTreeNode(activeProject)); } this.refreshCrossNodes(); } /** * refresh all of the cross nodes */ private void refreshCrossNodes() { ProjectTreeNode projectNode = (ProjectTreeNode)this.getModel().getRoot(); QtlProject project = projectNode.getProject(); QtlDataModel dataModel = project.getDataModel(); // remove tree nodes not in the QTL data model for(int crossNodeIndex = projectNode.getChildCount() - 1; crossNodeIndex >= 0; crossNodeIndex--) { CrossTreeNode currCrossNode = (CrossTreeNode)projectNode.getChildAt(crossNodeIndex); boolean crossInDataModel = dataModel.getCrossMap().containsKey( currCrossNode.getCross().getAccessorExpressionString()); if(!crossInDataModel) { this.getModel().removeNodeFromParent(currCrossNode); } } // add nodes that are missing (refresh existing) for(Cross currCross: dataModel.getCrossMap().values()) { int indexOfCross = SwingTreeUtilities.indexOfChildWithUserObject( projectNode, currCross); CrossTreeNode currCrossNode; if(indexOfCross == -1) { // append the cross to the end of the project node currCrossNode = new CrossTreeNode(currCross); this.getModel().insertNodeInto( currCrossNode, projectNode, projectNode.getChildCount()); this.getModel().insertNodeInto( currCrossNode.getScanOneFolderNode(), currCrossNode, currCrossNode.getChildCount()); this.getModel().insertNodeInto( currCrossNode.getScanTwoFolderNode(), currCrossNode, currCrossNode.getChildCount()); this.getModel().insertNodeInto( currCrossNode.getQtlBasketFolderNode(), currCrossNode, currCrossNode.getChildCount()); this.getModel().insertNodeInto( currCrossNode.getFitResultsFolderNode(), currCrossNode, currCrossNode.getChildCount()); this.expandPath(new TreePath( currCrossNode.getPath())); } else { currCrossNode = (CrossTreeNode)projectNode.getChildAt(indexOfCross); } this.refreshCross(currCrossNode); } this.getModel().nodeChanged(projectNode); } /** * Refresh the given cross * @param crossNodeToRefresh * the cross to refresh */ private void refreshCross(CrossTreeNode crossNodeToRefresh) { this.refreshScanOneFolder(crossNodeToRefresh.getScanOneFolderNode()); this.refreshScanTwoFolder(crossNodeToRefresh.getScanTwoFolderNode()); this.refreshQtlBasketFolder(crossNodeToRefresh.getQtlBasketFolderNode()); this.refreshFitResultsFolder(crossNodeToRefresh.getFitResultsFolderNode()); } /** * Refresh the given qtl basket folder * @param qtlBasketFolderNode * the qtl basket folder */ private void refreshQtlBasketFolder(QtlBasketFolderNode qtlBasketFolderNode) { Cross cross = qtlBasketFolderNode.getCross(); Map<String, QtlBasket> qtlBasketMap = cross.getQtlBasketMap(); boolean anyNewNodes = false; // remove tree nodes that are no longer in the cross synchronized(qtlBasketMap) { for(int crossChildIndex = qtlBasketFolderNode.getChildCount() - 1; crossChildIndex >= 0; crossChildIndex--) { TreeNode currCrossChild = qtlBasketFolderNode.getChildAt(crossChildIndex); if(currCrossChild instanceof QtlBasketNode) { QtlBasketNode currQtlBasketNode = (QtlBasketNode)currCrossChild; if(!qtlBasketMap.containsKey(currQtlBasketNode.getQtlBasket().getName())) { // the basket node needs to be removed this.getModel().removeNodeFromParent(currQtlBasketNode); } } } // add the child nodes that are missing from the tree for(QtlBasket currQtlBasket: qtlBasketMap.values()) { int indexOfBasket = SwingTreeUtilities.indexOfChildWithUserObject( qtlBasketFolderNode, currQtlBasket); if(indexOfBasket == -1) { QtlBasketNode newQtlBasketNode = new QtlBasketNode(currQtlBasket); this.getModel().insertNodeInto( newQtlBasketNode, qtlBasketFolderNode, qtlBasketFolderNode.getChildCount()); anyNewNodes = true; } } } if(anyNewNodes) { this.expandPath(new TreePath( qtlBasketFolderNode.getPath())); } this.getModel().nodeChanged(qtlBasketFolderNode); } /** * Refresh the scan one folder for a cross * @param scanOneFolder * the folder that we're refreshing */ private void refreshScanOneFolder(ScanOneFolderNode scanOneFolder) { Cross cross = scanOneFolder.getCross(); Set<ScanOneResult> scanOneResults = cross.getScanOneResults(); // remove tree nodes that are no longer in the cross for(int crossChildIndex = scanOneFolder.getChildCount() - 1; crossChildIndex >= 0; crossChildIndex--) { TreeNode currCrossChild = scanOneFolder.getChildAt(crossChildIndex); if(currCrossChild instanceof ScanOneTreeNode) { ScanOneTreeNode currScanOneNode = (ScanOneTreeNode)currCrossChild; if(!scanOneResults.contains(currScanOneNode.getScanOneResult())) { // the scan one node needs to be removed this.getModel().removeNodeFromParent(currScanOneNode); } } } // add the child nodes that are missing from the tree boolean anyNewNodes = false; for(ScanOneResult currScanOneResult: scanOneResults) { int indexOfScanResult = SwingTreeUtilities.indexOfChildWithUserObject( scanOneFolder, currScanOneResult); if(indexOfScanResult == -1) { ScanOneTreeNode newScanOneNode = new ScanOneTreeNode( currScanOneResult); this.getModel().insertNodeInto( newScanOneNode, scanOneFolder, scanOneFolder.getChildCount()); anyNewNodes = true; } } if(anyNewNodes) { this.expandPath(new TreePath( scanOneFolder.getPath())); } this.getModel().nodeChanged(scanOneFolder); } /** * Refresh the scan one folder for a cross * @param scanTwoFolder * the folder that we're refreshing */ private void refreshScanTwoFolder(ScanTwoFolderNode scanTwoFolder) { Cross cross = scanTwoFolder.getCross(); Set<ScanTwoResult> scanTwoResults = cross.getScanTwoResults(); // remove tree nodes that are no longer in the cross for(int crossChildIndex = scanTwoFolder.getChildCount() - 1; crossChildIndex >= 0; crossChildIndex--) { TreeNode currCrossChild = scanTwoFolder.getChildAt(crossChildIndex); if(currCrossChild instanceof ScanTwoTreeNode) { ScanTwoTreeNode currScanTwoNode = (ScanTwoTreeNode)currCrossChild; if(!scanTwoResults.contains(currScanTwoNode.getScanTwoResult())) { // the scan one node needs to be removed this.getModel().removeNodeFromParent(currScanTwoNode); } } } // add the child nodes that are missing from the tree boolean anyNewNodes = false; for(ScanTwoResult currScanTwoResult: scanTwoResults) { int indexOfScanResult = SwingTreeUtilities.indexOfChildWithUserObject( scanTwoFolder, currScanTwoResult); if(indexOfScanResult == -1) { ScanTwoTreeNode newScanTwoNode = new ScanTwoTreeNode( currScanTwoResult); this.getModel().insertNodeInto( newScanTwoNode, scanTwoFolder, scanTwoFolder.getChildCount()); anyNewNodes = true; } } if(anyNewNodes) { this.expandPath(new TreePath( scanTwoFolder.getPath())); } this.getModel().nodeChanged(scanTwoFolder); } private void refreshFitResultsFolder(FitResultsFolderNode fitResultsFolder) { Cross cross = fitResultsFolder.getCross(); Set<FitQtlResult> fitQtlResults = cross.getFitQtlResults(); // remove tree nodes that are no longer in the cross for(int crossChildIndex = fitResultsFolder.getChildCount() - 1; crossChildIndex >= 0; crossChildIndex--) { TreeNode currCrossChild = fitResultsFolder.getChildAt(crossChildIndex); if(currCrossChild instanceof FitResultsTreeNode) { FitResultsTreeNode currFitResultsTreeNode = (FitResultsTreeNode)currCrossChild; if(!fitQtlResults.contains(currFitResultsTreeNode.getFitQtlResult())) { // the fit node needs to be removed this.getModel().removeNodeFromParent(currFitResultsTreeNode); } } } // add the child nodes that are missing from the tree boolean anyNewNodes = false; for(FitQtlResult currFitQtlResult: fitQtlResults) { int indexOfFitResult = SwingTreeUtilities.indexOfChildWithUserObject( fitResultsFolder, currFitQtlResult); if(indexOfFitResult == -1) { FitResultsTreeNode newFitTreeNode = new FitResultsTreeNode( currFitQtlResult); this.getModel().insertNodeInto( newFitTreeNode, fitResultsFolder, fitResultsFolder.getChildCount()); anyNewNodes = true; } } if(anyNewNodes) { this.expandPath(new TreePath( fitResultsFolder.getPath())); } this.getModel().nodeChanged(fitResultsFolder); } /** * {@inheritDoc} */ @Override public DefaultTreeModel getModel() { return (DefaultTreeModel)super.getModel(); } /** * A folder node for holding scan one results */ private static class ScanOneFolderNode extends DefaultMutableTreeNode implements Iconifiable { /** * every {@link java.io.Serializable} is supposed to have one of these */ private static final long serialVersionUID = -8306864579326456888L; /** * the name this "folder" always has */ private static final String FOLDER_NAME = "One QTL Scan Results"; private static final String ICON_RESOURCE = "/images/scanone-16x16.png"; private static final Icon SHARED_ICON; static { URL iconUrl = ScanOneFolderNode.class.getResource(ICON_RESOURCE); SHARED_ICON = new ImageIcon(iconUrl); } /** * @see #getCross() */ private final Cross cross; /** * Constructor * @param cross * see {@link #getCross()} */ public ScanOneFolderNode(Cross cross) { this.cross = cross; } /** * Getter for the cross this scan one folder is for * @return the cross */ public Cross getCross() { return this.cross; } /** * {@inheritDoc} */ @Override public String toString() { int childCount = this.getChildCount(); return FOLDER_NAME + " (" + (childCount == 0 ? "empty" : childCount) + ")"; } /** * {@inheritDoc} */ public Icon getIcon() { return SHARED_ICON; } } /** * A folder node for holding scan one results */ private static class ScanTwoFolderNode extends DefaultMutableTreeNode implements Iconifiable { /** * every {@link java.io.Serializable} is supposed to have one of these */ private static final long serialVersionUID = -2517496924963282330L; /** * the name this "folder" always has */ private static final String FOLDER_NAME = "Two QTL Scan Results"; private static final String ICON_RESOURCE = "/images/scantwo-16x16.png"; private static final Icon SHARED_ICON; static { URL iconUrl = ScanTwoFolderNode.class.getResource(ICON_RESOURCE); SHARED_ICON = new ImageIcon(iconUrl); } /** * @see #getCross() */ private final Cross cross; /** * Constructor * @param cross * see {@link #getCross()} */ public ScanTwoFolderNode(Cross cross) { this.cross = cross; } /** * Getter for the cross this scan one folder is for * @return the cross */ public Cross getCross() { return this.cross; } /** * {@inheritDoc} */ @Override public String toString() { int childCount = this.getChildCount(); return FOLDER_NAME + " (" + (childCount == 0 ? "empty" : childCount) + ")"; } /** * {@inheritDoc} */ public Icon getIcon() { return SHARED_ICON; } } /** * A folder node for holding fit results */ private static class FitResultsFolderNode extends DefaultMutableTreeNode implements Iconifiable { /** * every {@link java.io.Serializable} is supposed to have one of these */ private static final long serialVersionUID = 1441161822612112130L; /** * the name this "folder" always has */ private static final String FOLDER_NAME = "Fit QTL Results"; private static final String ICON_RESOURCE = "/images/fitqtl-16x16.png"; private static final Icon SHARED_ICON; static { URL iconUrl = FitResultsFolderNode.class.getResource(ICON_RESOURCE); SHARED_ICON = new ImageIcon(iconUrl); } /** * @see #getCross() */ private final Cross cross; /** * Constructor * @param cross * see {@link #getCross()} */ public FitResultsFolderNode(Cross cross) { this.cross = cross; } /** * Getter for the cross this fitqtl folder is for * @return the cross */ public Cross getCross() { return this.cross; } /** * {@inheritDoc} */ @Override public String toString() { int childCount = this.getChildCount(); return FOLDER_NAME + " (" + (childCount == 0 ? "empty" : childCount) + ")"; } /** * {@inheritDoc} */ public Icon getIcon() { return SHARED_ICON; } } /** * node for holding scanone results */ private class ScanOneTreeNode extends DefaultMutableTreeNode implements MouseListener { /** * every {@link java.io.Serializable} is supposed to have one */ private static final long serialVersionUID = -4902056183384667513L; /** * Constructor * @param scanOneResult * the scan result */ public ScanOneTreeNode( ScanOneResult scanOneResult) { super(scanOneResult); } /** * Getter for the scan one result * @return * the scan result */ public ScanOneResult getScanOneResult() { return (ScanOneResult)this.getUserObject(); } /** * {@inheritDoc} */ @Override public String toString() { return this.getScanOneResult().toString(); } /** * {@inheritDoc} */ public void mouseClicked(MouseEvent e) { if(e.isPopupTrigger()) { this.popupTriggered(e); } } /** * Don't care * @param e * the event we don't care about */ public void mouseEntered(MouseEvent e) { } /** * Don't care * @param e * the event we don't care about */ public void mouseExited(MouseEvent e) { } /** * {@inheritDoc} */ public void mousePressed(MouseEvent e) { if(e.isPopupTrigger()) { this.popupTriggered(e); } } /** * {@inheritDoc} */ public void mouseReleased(MouseEvent e) { if(e.isPopupTrigger()) { this.popupTriggered(e); } } /** * Respond to a popup trigger event. * @param e * the event we're responding to */ @SuppressWarnings("serial") private void popupTriggered(MouseEvent e) { JPopupMenu popupMenu = new JPopupMenu( this.getScanOneResult().toString()); popupMenu.add(new ScanOneSummaryAction( this.getScanOneResult())); popupMenu.add(new PlotScanOneResultAction( this.getScanOneResult())); popupMenu.addSeparator(); popupMenu.add(new SafeDeleteAction( this.getScanOneResult().toString(), e.getComponent(), QTL.getInstance().getDesktop()) { /** * {@inheritDoc} */ @Override public void delete() { ScanOneTreeNode.this.delete(); } }); popupMenu.show( (Component)e.getSource(), e.getX(), e.getY()); } /** * Delete this scanone */ private void delete() { RInterface rInterface = RInterfaceFactory.getRInterfaceInstance(); rInterface.insertComment( "Deleting scanone object"); rInterface.evaluateCommand( "rm(" + this.getScanOneResult().getAccessorExpressionString() + ")"); QtlProjectManager projectManager = QtlProjectTree.this.getProjectManager(); projectManager.notifyActiveProjectModified(); projectManager.refreshProjectDataStructures(); } } /** * Node for holding fit results */ private class FitResultsTreeNode extends DefaultMutableTreeNode implements MouseListener { /** * every {@link java.io.Serializable} is supposed to have one of these */ private static final long serialVersionUID = -3610675330618423138L; /** * Constructor * @param fitQtlResult * see {@link #getFitQtlResult()} */ public FitResultsTreeNode(FitQtlResult fitQtlResult) { super(fitQtlResult); } /** * Getter for the fit result * @return the fitQtlResult */ public FitQtlResult getFitQtlResult() { return (FitQtlResult)this.getUserObject(); } /** * {@inheritDoc} */ public void mouseClicked(MouseEvent e) { if(e.isPopupTrigger()) { this.popupTriggered(e); } } /** * Don't care * @param e * the event we don't care about */ public void mouseEntered(MouseEvent e) { } /** * Don't care * @param e * the event we don't care about */ public void mouseExited(MouseEvent e) { } /** * {@inheritDoc} */ public void mousePressed(MouseEvent e) { if(e.isPopupTrigger()) { this.popupTriggered(e); } } /** * {@inheritDoc} */ public void mouseReleased(MouseEvent e) { if(e.isPopupTrigger()) { this.popupTriggered(e); } } /** * Respond to a popup trigger event. * @param e * the event we're responding to */ @SuppressWarnings("serial") private void popupTriggered(MouseEvent e) { JPopupMenu popupMenu = new JPopupMenu( this.getFitQtlResult().toString()); popupMenu.add(new ShowFitQtlResultsAction( this.getFitQtlResult())); popupMenu.addSeparator(); popupMenu.add(new SafeDeleteAction( this.getFitQtlResult().toString(), e.getComponent(), QTL.getInstance().getDesktop()) { /** * {@inheritDoc} */ @Override public void delete() { FitResultsTreeNode.this.delete(); } }); popupMenu.show( (Component)e.getSource(), e.getX(), e.getY()); } /** * delete this fit */ private void delete() { RInterface rInterface = RInterfaceFactory.getRInterfaceInstance(); rInterface.insertComment( "Deleting fit result"); rInterface.evaluateCommand( "rm(" + this.getFitQtlResult().getAccessorExpressionString() + ")"); QtlProjectManager projectManager = QtlProjectTree.this.getProjectManager(); projectManager.notifyActiveProjectModified(); projectManager.refreshProjectDataStructures(); } /** * {@inheritDoc} */ @Override public String toString() { return this.getFitQtlResult().toString(); } } /** * node for holding scantwo results */ private class ScanTwoTreeNode extends DefaultMutableTreeNode implements MouseListener { /** * every {@link java.io.Serializable} is supposed to have one */ private static final long serialVersionUID = 6030556705431694911L; /** * Constructor * @param scanTwoResult * the scan result */ public ScanTwoTreeNode( ScanTwoResult scanTwoResult) { super(scanTwoResult); } /** * Getter for the scan one result * @return * the scan result */ public ScanTwoResult getScanTwoResult() { return (ScanTwoResult)this.getUserObject(); } /** * {@inheritDoc} */ @Override public String toString() { return this.getScanTwoResult().toString(); } /** * {@inheritDoc} */ public void mouseClicked(MouseEvent e) { if(e.isPopupTrigger()) { this.popupTriggered(e); } } /** * Don't care * @param e * the event we don't care about */ public void mouseEntered(MouseEvent e) { } /** * Don't care * @param e * the event we don't care about */ public void mouseExited(MouseEvent e) { } /** * {@inheritDoc} */ public void mousePressed(MouseEvent e) { if(e.isPopupTrigger()) { this.popupTriggered(e); } } /** * {@inheritDoc} */ public void mouseReleased(MouseEvent e) { if(e.isPopupTrigger()) { this.popupTriggered(e); } } /** * Respond to a popup trigger event. * @param e * the event we're responding to */ @SuppressWarnings("serial") private void popupTriggered(MouseEvent e) { JPopupMenu popupMenu = new JPopupMenu( this.getScanTwoResult().toString()); popupMenu.add(new ScanTwoSummaryAction( this.getScanTwoResult())); popupMenu.add(new PlotScanTwoResultAction( this.getScanTwoResult())); popupMenu.addSeparator(); popupMenu.add(new SafeDeleteAction( this.getScanTwoResult().toString(), e.getComponent(), QTL.getInstance().getDesktop()) { /** * {@inheritDoc} */ @Override public void delete() { ScanTwoTreeNode.this.delete(); } }); popupMenu.show( (Component)e.getSource(), e.getX(), e.getY()); } /** * delete this scantwo */ private void delete() { RInterface rInterface = RInterfaceFactory.getRInterfaceInstance(); rInterface.insertComment( "Deleting scantwo object"); rInterface.evaluateCommand( "rm(" + this.getScanTwoResult().getAccessorExpressionString() + ")"); QtlProjectManager projectManager = QtlProjectTree.this.getProjectManager(); projectManager.notifyActiveProjectModified(); projectManager.refreshProjectDataStructures(); } } /** * A folder node for holding scan one results */ private static class QtlBasketFolderNode extends DefaultMutableTreeNode implements Iconifiable { /** * every {@link java.io.Serializable} is supposed to have one of these */ private static final long serialVersionUID = -4193900392082425451L; /** * the name this "folder" always has */ private static final String FOLDER_NAME = "QTL Baskets"; private static final String ICON_RESOURCE = "/images/qtl-basket-16x16.png"; private static final Icon SHARED_ICON; static { URL iconUrl = QtlBasketFolderNode.class.getResource(ICON_RESOURCE); SHARED_ICON = new ImageIcon(iconUrl); } /** * @see #getCross() */ private final Cross cross; /** * Constructor * @param cross * see {@link #getCross()} */ public QtlBasketFolderNode(Cross cross) { this.cross = cross; } /** * Getter for the cross this scan one folder is for * @return the cross */ public Cross getCross() { return this.cross; } /** * {@inheritDoc} */ @Override public String toString() { int childCount = this.getChildCount(); return FOLDER_NAME + " (" + (childCount == 0 ? "empty" : childCount) + ")"; } /** * {@inheritDoc} */ public Icon getIcon() { return SHARED_ICON; } } /** * node for holding QTL baskets */ private class QtlBasketNode extends DefaultMutableTreeNode implements MouseListener { /** * every {@link java.io.Serializable} is supposed to have one of these */ private static final long serialVersionUID = 9222373128292290190L; /** * Constructor * @param qtlBasket * the basket */ public QtlBasketNode(QtlBasket qtlBasket) { super(qtlBasket); } /** * Getter for the QTL basket * @return * the QTL basket */ public QtlBasket getQtlBasket() { return (QtlBasket)this.getUserObject(); } /** * {@inheritDoc} */ @Override public String toString() { return this.getQtlBasket().toString(); } /** * {@inheritDoc} */ public void mouseClicked(MouseEvent e) { if(e.isPopupTrigger()) { this.popupTriggered(e); } } /** * Don't care * @param e * the event we don't care about */ public void mouseEntered(MouseEvent e) { } /** * Don't care * @param e * the event we don't care about */ public void mouseExited(MouseEvent e) { } /** * {@inheritDoc} */ public void mousePressed(MouseEvent e) { if(e.isPopupTrigger()) { this.popupTriggered(e); } } /** * {@inheritDoc} */ public void mouseReleased(MouseEvent e) { if(e.isPopupTrigger()) { this.popupTriggered(e); } } /** * Respond to a popup trigger event. * @param e * the event we're responding to */ @SuppressWarnings("serial") private void popupTriggered(MouseEvent e) { JPopupMenu popupMenu = new JPopupMenu(); popupMenu.add(new EditQtlBasketAction(this.getQtlBasket())); popupMenu.add(new FitQtlAction( this.getQtlBasket())); popupMenu.addSeparator(); popupMenu.add(new SafeDeleteAction( this.getQtlBasket().getName(), e.getComponent(), QTL.getInstance().getDesktop()) { /** * {@inheritDoc} */ @Override public void delete() { QtlBasketNode.this.delete(); } }); popupMenu.show( (Component)e.getSource(), e.getX(), e.getY()); } /** * Delete this qtl basket */ private void delete() { this.getQtlBasket().getParentCross().getQtlBasketMap().remove( this.getQtlBasket().getName()); QtlProjectManager projectManager = QtlProjectTree.this.getProjectManager(); projectManager.notifyActiveProjectModified(); projectManager.refreshProjectDataStructures(); } } /** * node for holding cross objects */ private class CrossTreeNode extends DefaultMutableTreeNode implements MouseListener, Iconifiable { /** * every {@link java.io.Serializable} is supposed to have one * of these */ private static final long serialVersionUID = 1271152284893969725L; private static final String ICON_RESOURCE = "/images/cross-16x16.png"; private final Icon icon; /** * @see #getScanOneFolderNode() */ private final ScanOneFolderNode scanOneFolderNode; /** * @see #getScanTwoFolderNode() */ private final ScanTwoFolderNode scanTwoFolderNode; /** * @see #getQtlBasketFolderNode() */ private final QtlBasketFolderNode qtlBasketFolderNode; /** * @see #getFitResultsFolderNode() */ private final FitResultsFolderNode fitResultsFolderNode; /** * Constructor * @param cross * the cross */ public CrossTreeNode(Cross cross) { super(cross); this.icon = new ImageIcon(CrossTreeNode.class.getResource( ICON_RESOURCE)); this.scanOneFolderNode = new ScanOneFolderNode(cross); this.scanTwoFolderNode = new ScanTwoFolderNode(cross); this.qtlBasketFolderNode = new QtlBasketFolderNode(cross); this.fitResultsFolderNode = new FitResultsFolderNode(cross); } /** * Getter for the {@link FitResultsFolderNode} * @return * the fit folder */ public FitResultsFolderNode getFitResultsFolderNode() { return this.fitResultsFolderNode; } /** * Getter for the {@link QtlBasketFolderNode} * @return * QTL basket folder */ public QtlBasketFolderNode getQtlBasketFolderNode() { return this.qtlBasketFolderNode; } /** * Getter for the {@link ScanOneFolderNode} * @return * the scan one folder */ public ScanOneFolderNode getScanOneFolderNode() { return this.scanOneFolderNode; } /** * Getter for the {@link ScanTwoFolderNode} * @return * the scan two folder node */ public ScanTwoFolderNode getScanTwoFolderNode() { return this.scanTwoFolderNode; } /** * Getter for the cross * @return * the cross */ public Cross getCross() { return (Cross)this.getUserObject(); } /** * {@inheritDoc} */ @Override public String toString() { return this.getCross().toString(); } /** * {@inheritDoc} */ public void mouseClicked(MouseEvent e) { if(e.isPopupTrigger()) { this.popupTriggered(e); } } /** * Don't care * @param e * the event we don't care about */ public void mouseEntered(MouseEvent e) { } /** * Don't care * @param e * the event we don't care about */ public void mouseExited(MouseEvent e) { } /** * {@inheritDoc} */ public void mousePressed(MouseEvent e) { if(e.isPopupTrigger()) { this.popupTriggered(e); } } /** * {@inheritDoc} */ public void mouseReleased(MouseEvent e) { if(e.isPopupTrigger()) { this.popupTriggered(e); } } /** * Respond to a popup trigger event. * @param e * the event we're responding to */ @SuppressWarnings("serial") private void popupTriggered(MouseEvent e) { Cross cross = this.getCross(); JPopupMenu popupMenu = new JPopupMenu( cross.toString()); popupMenu.add(new ShowCrossSummaryAction(cross)); popupMenu.add(new RunJittermapAction(cross)); popupMenu.addSeparator(); popupMenu.add(new SafeDeleteAction( this.getCross().toString(), e.getComponent(), QTL.getInstance().getDesktop()) { /** * {@inheritDoc} */ @Override public void delete() { CrossTreeNode.this.delete(); } }); popupMenu.show( (Component)e.getSource(), e.getX(), e.getY()); } /** * Delete this cross */ private void delete() { RInterface rInterface = RInterfaceFactory.getRInterfaceInstance(); rInterface.insertComment("Deleting cross object"); for(ScanTwoResult scanTwoResult: this.getCross().getScanTwoResults()) { rInterface.evaluateCommand( "rm(" + scanTwoResult.getAccessorExpressionString() + ")"); } for(ScanOneResult scanOneResult: this.getCross().getScanOneResults()) { rInterface.evaluateCommand( "rm(" + scanOneResult.getAccessorExpressionString() + ")"); } for(FitQtlResult fitResult: this.getCross().getFitQtlResults()) { rInterface.evaluateCommand( "rm(" + fitResult.getAccessorExpressionString() + ")"); } rInterface.evaluateCommand( "rm(" + this.getCross().getAccessorExpressionString() + ")"); QtlProjectManager projectManager = QtlProjectTree.this.getProjectManager(); projectManager.notifyActiveProjectModified(); projectManager.refreshProjectDataStructures(); } /** * {@inheritDoc} */ public Icon getIcon() { return this.icon; } } /** * tree node for project */ private static class ProjectTreeNode extends DefaultMutableTreeNode implements MouseListener, Iconifiable { /** * every {@link java.io.Serializable} is supposed to have one of these */ private static final long serialVersionUID = -7499045875913172724L; private static final String ICON_RESOURCE = "/images/project-16x16.png"; private static final Icon SHARED_ICON; static { URL iconUrl = ProjectTreeNode.class.getResource(ICON_RESOURCE); SHARED_ICON = new ImageIcon(iconUrl); } /** * the String to use if {@link QtlProject#getName()} is null */ private static final String DEFAULT_PROJECT_NAME = "New J/qtl Project"; /** * @param project */ public ProjectTreeNode(QtlProject project) { super(project); } /** * {@inheritDoc} */ @Override public String toString() { int childCount = this.getChildCount(); String childCountString = " (" + (childCount == 0 ? "empty" : childCount) + ")"; String name = this.getProject().getName(); return (name == null ? DEFAULT_PROJECT_NAME : name) + childCountString; } /** * Getter for the project * @return the project */ public QtlProject getProject() { return (QtlProject)this.getUserObject(); } /** * {@inheritDoc} */ public void mouseClicked(MouseEvent e) { if(e.isPopupTrigger()) { this.popupTriggered(e); } } /** * Don't care * @param e * the event we don't care about */ public void mouseEntered(MouseEvent e) { } /** * Don't care * @param e * the event we don't care about */ public void mouseExited(MouseEvent e) { } /** * {@inheritDoc} */ public void mousePressed(MouseEvent e) { if(e.isPopupTrigger()) { this.popupTriggered(e); } } /** * {@inheritDoc} */ public void mouseReleased(MouseEvent e) { if(e.isPopupTrigger()) { this.popupTriggered(e); } } /** * Respond to a popup trigger event. * @param e * the event we're responding to */ private void popupTriggered(MouseEvent e) { // TODO implement me } /** * {@inheritDoc} */ public Icon getIcon() { return SHARED_ICON; } } }