package org.ut.biolab.medsavant.shared.appdevapi; import com.healthmarketscience.sqlbuilder.BinaryCondition; import com.healthmarketscience.sqlbuilder.ComboCondition; import java.awt.Color; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.io.File; import java.io.FileWriter; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.swing.JButton; import javax.swing.JDialog; import javax.swing.JPanel; import javax.swing.JTextField; import medsavant.discovery.DiscoveryFindings; import org.ut.biolab.medsavant.client.geneset.GeneSetController; import org.ut.biolab.medsavant.client.project.ProjectController; import org.ut.biolab.medsavant.client.query.SearchConditionItem; import org.ut.biolab.medsavant.client.query.medsavant.complex.ComprehensiveConditionGenerator; import org.ut.biolab.medsavant.client.query.medsavant.complex.GenesConditionGenerator; import org.ut.biolab.medsavant.client.query.medsavant.complex.OntologyConditionGenerator; import org.ut.biolab.medsavant.client.query.view.GeneSearchConditionEditorView; import org.ut.biolab.medsavant.client.query.view.SearchConditionEditorView; import org.ut.biolab.medsavant.client.query.view.SearchConditionPanel; import org.ut.biolab.medsavant.client.region.RegionController; import org.ut.biolab.medsavant.client.util.ClientMiscUtils; import org.ut.biolab.medsavant.client.util.ClientNetworkUtils; import org.ut.biolab.medsavant.client.view.util.DialogUtils; import org.ut.biolab.medsavant.shared.db.TableSchema; import org.ut.biolab.medsavant.shared.format.BasicVariantColumns; import org.ut.biolab.medsavant.shared.importing.BEDFormat; import org.ut.biolab.medsavant.shared.importing.FileFormat; import org.ut.biolab.medsavant.shared.model.Gene; import org.ut.biolab.medsavant.shared.model.GenomicRegion; import org.ut.biolab.medsavant.shared.model.OntologyType; import org.ut.biolab.medsavant.shared.model.RegionSet; /** * Creates a gene panel entry form. * * @author rammar */ public class GenePanel { public static final String PANEL= "panel"; public static final String HPO= "hpo"; private static final String DONE_TEXT= "Apply"; private static final String CLEAR_TEXT= "Clear"; private static final String SAVE_TEXT= "Save panel"; private static final String SAVED_TEXT= "Saved!"; private static final String DEFAULT_GENE_PANEL_TITLE_TEXT= "Save gene panel as..."; private Matcher m; private String panelType; private ComprehensiveConditionGenerator ccg; private SearchConditionItem sci; private SearchConditionEditorView scev; private SearchConditionPanel scp; private String encodedSearch; private ComboCondition genePanelCC= new ComboCondition(ComboCondition.Op.OR); // initialize to something private JButton doneButton; private RegionController controller= RegionController.getInstance(); private List<Gene> geneList= new ArrayList<Gene>(); // The list of gene objects created from gene symbols. private JTextField genePanelTitle= new JTextField(DEFAULT_GENE_PANEL_TITLE_TEXT); private Map<String, String> columns= new DiscoveryFindings(null).getDbToHumanReadableMap(); private TableSchema ts= ProjectController.getInstance().getCurrentVariantTableSchema(); private OntologyConditionGenerator ocg; private JDialog displayDialog; private JButton saveButton; /** * Create a new GenePanel entry form. * @param type Type of gene panel. Can be GenePanel.PANEL or GenePanel.HPO. If wrong type is specified, defaults to gene panel */ public GenePanel(String type) { panelType= type; sci= new SearchConditionItem("", null); if (type.equals(GenePanel.HPO)) { ccg= new OntologyConditionGenerator(OntologyType.HPO); ocg= new OntologyConditionGenerator(OntologyType.HPO); } else { //default to gene panel ccg= new GenesConditionGenerator(); } scev= ccg.getViewGeneratorForItem(sci); createSearchConditionPanel(); } /** * Create a new GenePanel entry form designed for display in a JDialog. * @param jd The JDialog where it will be displayed * @param type Type of gene panel. Can be GenePanel.PANEL or GenePanel.HPO. If wrong type is specified, defaults to gene panel */ public GenePanel(String type, JDialog jd) { this(type); this.displayDialog= jd; } /** * Define exception for GenePanel. */ private class GenePanelException extends Exception { public GenePanelException(String message) { super(message); } } /** * Return the custom gene panel JPanel using GenesConditionGenerator. * @return the JPanel responsible for gene panel entry and saving. */ public JPanel getSearchConditionPanel() { if (scp == null) { createSearchConditionPanel(); } else { // reset doneButton from clear to done doneButton.setText(DONE_TEXT); } return scp; } /** * Create a custom gene panel JPanel using GenesConditionGenerator. */ private void createSearchConditionPanel() { scp= new SearchConditionPanel(scev, null); // initialize here, not construct // components of this panel doneButton= new JButton(DONE_TEXT); genePanelTitle.setForeground(Color.RED); /* Button to indicate the gene panel selection is ready for variant filtering. */ doneButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent ae) { if (doneButton.getText().equals(DONE_TEXT)) { doneAction(); } else if (doneButton.getText().equals(CLEAR_TEXT)) { clearAction(); } } }); /* Add the gene panel title text field when hovering over the save button. */ saveButton= new JButton("Save panel"); if (panelType.equals(GenePanel.HPO)) saveButton.setText("Save as gene panel"); // to reduce ambiguity saveButton.addMouseMotionListener( new MouseMotionListener() { @Override public void mouseMoved(MouseEvent me) { genePanelTitle.addMouseListener( new MouseListener() { @Override public void mouseClicked(MouseEvent me) { genePanelTitle.setText(""); genePanelTitle.setForeground(Color.BLACK); } // remaining methods included but do nothing @Override public void mouseExited(MouseEvent me) {} @Override public void mouseReleased(MouseEvent me) {} @Override public void mousePressed(MouseEvent me) {} @Override public void mouseEntered(MouseEvent me) {} } ); // Add the title bar on top of the buttons if it hasn't // already been added if (genePanelTitle.getParent() != scp) { scp.add(genePanelTitle, 2); scp.revalidate(); } } @Override public void mouseDragged(MouseEvent me) {} } ); /* Save the gene panel as a region list on the server, as long as there * is a specified panel title. */ saveButton.addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent ae) { /* Perform done button actions before saving. */ doneAction(); /* Save the panel as a region list on the server if the name * has been assigned but doesn't exist already. */ // match empty strings or plain whitespace m= (Pattern.compile("\\s*")).matcher(genePanelTitle.getText()); if (!genePanelTitle.getText().equals(DEFAULT_GENE_PANEL_TITLE_TEXT) && !m.matches() && validateGenePanelTitle(genePanelTitle.getText())) { saveGenePanel(genePanelTitle.getText(), geneList); saveButton.setText(SAVED_TEXT); } else { DialogUtils.displayError("Oops!", "Please enter a valid gene panel title."); } } } ); /* Add components to the panel. */ scp.getButtonPanel().add(saveButton); scp.getButtonPanel().add(doneButton); /* Retrieve the panel. If displaying in a JDialog, pass a non-null * runnable to pack the JDialog. */ if (displayDialog == null) { scp.loadEditorViewInBackground(null); } else { scp.loadEditorViewInBackground( new Runnable() { @Override public void run() { displayDialog.pack(); displayDialog.invalidate(); // not sure if this is necessary } } ); } } /** * Action performed when "Done" button is clicked. */ private void doneAction() { // Clear the current list of Genes geneList.clear(); try { /* Save changes: this saves the users selections so next time * the dialog pops up, those same selections will be checked. * For most SearchConditionEditorViews, this isn't necessary, * but it is necessary for some (e.g. GenesConditionGenerator). * Best to always call it. */ if (scev.saveChanges()) { /* Get the gene symbols delimited by ";" */ encodedSearch= sci.getSearchConditionEncoding(); /* Only update the button if the string of genes has length > 0. */ if (encodedSearch.length() != 0) doneButton.setText(CLEAR_TEXT); /* Look up the chromosomal coordinates for each gene before saving */ String[] genes= null; if (panelType.equals(GenePanel.PANEL)) { genes= encodedSearch.split(";"); } else if (panelType.equals(GenePanel.HPO)) { // retrieve genes for each ontology term List<GenomicRegion> regionList= ocg.getRegionsFromEncoding(encodedSearch); genes= new String[regionList.size()]; for (int i= 0; i != regionList.size(); ++i) { genes[i]= regionList.get(i).getName(); } } // Use ORs between genes for a gene panel genePanelCC= new ComboCondition(ComboCondition.Op.OR); /* Add a condition for each gene symbol. */ for (String geneSymbol : genes) { Gene g= GeneSetController.getInstance().getGene(geneSymbol); if (g != null) { geneList.add(g); /* Search for the pattern "GENENAME:" in the jannovar symbol field. * Since there can be multiple genes in a field, I have two * conditions below. */ genePanelCC.addCondition( BinaryCondition.iLike(ts.getDBColumn(columns.get( BasicVariantColumns.JANNOVAR_SYMBOL.getAlias())), geneSymbol + ":%")); genePanelCC.addCondition( BinaryCondition.iLike(ts.getDBColumn(columns.get( BasicVariantColumns.JANNOVAR_SYMBOL.getAlias())), "%:" + geneSymbol + ":%")); } else { // Gene symbol not found in our DB throw new GenePanelException( "Could not retrieve details for gene symbol: " + geneSymbol); } } } } catch (Exception ex) { DialogUtils.displayError(ex.getMessage()); ex.printStackTrace(); } // Hide the JDialog if (displayDialog != null) displayDialog.setVisible(false); } /** * Action performed when "Done" button is clicked. */ private void clearAction() { try { saveButton.setText(SAVE_TEXT); doneButton.setText(DONE_TEXT); if (panelType.equals(GenePanel.PANEL)) { GeneSearchConditionEditorView temp= (GeneSearchConditionEditorView) scev; temp.clearTextArea(); } genePanelTitle.setText(DEFAULT_GENE_PANEL_TITLE_TEXT); genePanelTitle.setForeground(Color.RED); scp.remove(genePanelTitle); scp.revalidate(); // Clear the current list of Genes geneList.clear(); // Reset the ComboCondition to empty genePanelCC= new ComboCondition(ComboCondition.Op.OR); } catch (Exception e) { e.printStackTrace(); } } /** * Get the GenePanel ComboCondition. * @return the ComboCondition corresponding to this gene panel */ public ComboCondition getComboCondition() { return this.genePanelCC; } /** * Clear/reset the ComboCondition to empty */ public void clearCondition() { genePanelCC= new ComboCondition(ComboCondition.Op.OR); } /** * Set the background color. * @param c The Color */ public void setBackground(Color c) { scev.setBackground(c); scp.setBackground(c); scp.getButtonPanel().setBackground(c); } /** * Saves the gene list as a gene panel for this project in MedSavant. * @param panelTitle The gene panel title * @param geneList The gene panel as a List of Gene objects */ private void saveGenePanel(String panelTitle, List<Gene> geneList) { try { /* Create a temp file and send that to the server for import. * NOTE: I'm using the BED order that is used elsewhere in MedSavant, * but I'm not sure why it's designed this way. */ File tempFile = File.createTempFile("genes", ".bed"); FileWriter output = new FileWriter(tempFile); for (Gene g : geneList) { output.write(g.getChrom() + "\t" + g.getStart() + "\t" + g.getEnd() + "\t" + g.getName() + "\n"); } output.close(); char delim = '\t'; int numHeaderLines = 0; FileFormat fileFormat = new BEDFormat(); String path = tempFile.getAbsolutePath(); int transferID = ClientNetworkUtils.copyFileToServer(new File(path)); controller.addRegionSet(panelTitle, delim, fileFormat, numHeaderLines, transferID); tempFile.delete(); // remove the temporary file } catch (Exception e) { e.printStackTrace(); } } /** * Check to gene panel title is already in use. * @param panelTitle The gene panel title * @return boolean if name already exists */ private boolean validateGenePanelTitle(String panelTitle) { try { for (RegionSet r : controller.getRegionSets()) { if (r.getName().equals(panelTitle)) { DialogUtils.displayError("Oops!", "Gene panel name already in use."); return false; } } return true; } catch (Exception ex) { ClientMiscUtils.reportError("Error fetching region list: %s", ex); return false; } } /** * Update the gene panel combo condition based on an existing gene panel * stored as a region list on the server. * @param name The name of the gene panel/region list */ public void addCustomGenePanel(String name) { // Reset the combo condition, use ORs between genes for a gene panel genePanelCC= new ComboCondition(ComboCondition.Op.OR); // Get the list of region lists (gene panels) and find the one we're // interested in. try { List<RegionSet> allGenePanels= RegionController.getInstance().getRegionSets(); for (RegionSet rs : allGenePanels) { if (rs.getName().equals(name)) { List<GenomicRegion> allGenes= RegionController.getInstance().getRegionsInSet(rs); // iterate through all genes in this panel for (GenomicRegion gr : allGenes) { // add the gene name/symbol to the combo condition genePanelCC.addCondition(BinaryCondition.iLike( ts.getDBColumn(columns.get( BasicVariantColumns.JANNOVAR_SYMBOL.getAlias())), "%" + gr.getName() + ":%")); } } } } catch (Exception e) { e.printStackTrace(); } } }