package com.compomics.util.gui.protein; import com.compomics.util.Util; import java.awt.Color; import java.awt.FontMetrics; import java.util.ArrayList; import java.util.HashMap; import java.util.TreeMap; import javax.swing.JEditorPane; import javax.swing.SwingUtilities; import javax.swing.UIManager; /** * This class contains a method that formats a given protein sequence such that * both the covered parts of the sequence and the peptide selected in the peptide * table is highlighted. The result is inserted into a JEditorPane. * * @author Harald Barsnes */ public class ProteinSequencePane { /** * The map of PEFF key value pairs. */ private static TreeMap<String, String> peffKeyValuePairs; /** * Formats the protein sequence such that the covered parts of the sequence * is highlighted. The result is inserted into the provided JEditorPane. * * @param editorPane the editor pane to add the formatted sequence to * @param cleanSequence the clean protein sequence, i.e., just the amino acid sequence * @param coverage the sequence coverage map with numbers indicating the number * of times a given residue is used, zero means no coverage, * (note: only uses index 1-n) * @param keyValuePairs the key value pairs used for PEFF formating * @param tagColors the colors to use for the different tags, key is the tag * @param showModifications if the modifications are to be highlighted or not * @param showVariants if the variants are to be highlighted or not * @param showCoverage if the coverage is to be highlighted or not * @return the calculated sequence coverage in percent (0-100) */ public static double formatProteinSequence(JEditorPane editorPane, String cleanSequence, int[] coverage, TreeMap<String, String> keyValuePairs, HashMap<String, Color> tagColors, boolean showModifications, boolean showVariants, boolean showCoverage) { return formatProteinSequence(editorPane, cleanSequence, -1, -1, coverage, keyValuePairs, tagColors); } /** * Formats the protein sequence such that the covered parts of the sequence * is highlighted. The result is inserted into the provided JEditorPane. * * @param editorPane the editor pane to add the formatted sequence to * @param cleanSequence the clean protein sequence, i.e., just the amino acid sequence * @param coverage the sequence coverage map with numbers indicating the number * of times a given residue is used, zero means no coverage, * (note: only uses index 1-n) * @return the calculated sequence coverage in percent (0-100) */ public static double formatProteinSequence(JEditorPane editorPane, String cleanSequence, int[] coverage) { return formatProteinSequence(editorPane, cleanSequence, -1, -1, coverage, new TreeMap<String, String>(), new HashMap<String, Color>()); } /** * Formats the protein sequence such that the covered parts of the sequence * is highlighted. The result is inserted into the provided JEditorPane. * * @param editorPane the editor pane to add the formatted sequence to * @param cleanSequence the clean protein sequence, i.e., just the amino acid sequence * @param selectedPeptideStart the starting index of the selected peptide * @param selectedPeptideEnd the ending index of the selected peptide * @param coverage the sequence coverage map with numbers indicating the number * of times a given residue is used, zero means no coverage, * (note: only uses index 1-n) * @return the calculated sequence coverage in percent (0-100) */ public static double formatProteinSequence(JEditorPane editorPane, String cleanSequence, int selectedPeptideStart, int selectedPeptideEnd, int[] coverage) { return formatProteinSequence(editorPane, cleanSequence, selectedPeptideStart, selectedPeptideEnd, coverage, new TreeMap<String, String>(), new HashMap<String, Color>()); } /** * Formats the protein sequence such that both the covered parts of the sequence * and the peptide selected in the peptide table is highlighted. The result is * inserted into the provided JEditorPane. This method accounts for redundancies of the selected peptide in the protein sequence * * @param editorPane the editor pane to add the formatted sequence to * @param cleanSequence the clean protein sequence, i.e., just the amino acid sequence * @param selectedPeptideStart the start indexes of the currently selected peptide * @param selectedPeptideEnd the end indexes if the currently selected peptide * @param coverage the sequence coverage map with numbers indicating the number * of times a given residue is used, zero means no coverage, * (note: only uses index 1-n) * @return the calculated sequence coverage in percent (0-100) */ public static double formatProteinSequence(JEditorPane editorPane, String cleanSequence, ArrayList<Integer> selectedPeptideStart, ArrayList<Integer> selectedPeptideEnd, int[] coverage) { if (cleanSequence.length() != coverage.length - 1) { throw new IllegalArgumentException("The lenght of the coverage map has to be equal to the lenght of the sequence + 1!"); } String sequenceTable = "", currentCellSequence = ""; boolean selectedPeptide = false, coveredPeptide = false; double sequenceCoverage = 0; // see how many amino acids we have room for FontMetrics fm = editorPane.getGraphics().getFontMetrics(); int fontWidth = fm.stringWidth("X"); // hardcoding needed to due issues with other look and feels if (!UIManager.getLookAndFeel().getName().equalsIgnoreCase("Nimbus")) { fontWidth = 8; // 8 is to represent the default average font width in html // @TODO: find a way of removing this hardcoding... } int indexWidth = 200; if (cleanSequence.length() > 999) { indexWidth += 50; } double temp = (editorPane.getParent().getWidth() - indexWidth) / fontWidth; int numberOfAminoAcidsPerRow = (int) temp / 10; numberOfAminoAcidsPerRow *= 10; ArrayList<Integer> referenceMarkers = new ArrayList<Integer>(); boolean previousAminoAcidWasCovered = false; boolean previousAminoAcidWasSelected = false; // iterate the coverage table and create the formatted sequence string for (int i = 1; i < coverage.length; i++) { // add residue number and line break if (i % numberOfAminoAcidsPerRow == 1 || i == 1) { sequenceTable += "</tr><tr><td><font color=black><a name=\"" + i + "\">" + i + "</a></font></td>"; referenceMarkers.add(i); } // check if the current residues is covered if (coverage[i] > 0) { sequenceCoverage++; coveredPeptide = true; } else { coveredPeptide = false; } // check if the current residue is contained in the selected peptide for (int possibleStart : selectedPeptideStart) { if (i == possibleStart) { selectedPeptide = true; } } for (int possibleEnd : selectedPeptideEnd) { if (i == possibleEnd) { selectedPeptide = false; } } if (previousAminoAcidWasSelected && !selectedPeptide) { currentCellSequence += "</span>"; } // highlight the covered and selected peptides if (selectedPeptide) { if (i % 10 == 1) { currentCellSequence += "<span style=\"background:#CEE3F6\">" + cleanSequence.charAt(i - 1); } else { if (previousAminoAcidWasSelected) { currentCellSequence += cleanSequence.charAt(i - 1); } else { currentCellSequence += "</span><span style=\"background:#CEE3F6\">" + cleanSequence.charAt(i - 1); } } previousAminoAcidWasSelected = true; } else { previousAminoAcidWasSelected = false; if (coveredPeptide) { if (i % 10 == 1) { currentCellSequence += cleanSequence.charAt(i - 1); } else { if (previousAminoAcidWasCovered) { currentCellSequence += cleanSequence.charAt(i - 1); } else { currentCellSequence += "</span>" + cleanSequence.charAt(i - 1); } } } else { if (i % 10 == 1) { currentCellSequence += "<span style=\"color:#BDBDBD\">" + cleanSequence.charAt(i - 1); } else { if (previousAminoAcidWasCovered) { currentCellSequence += "<span style=\"color:#BDBDBD\">" + cleanSequence.charAt(i - 1); } else { currentCellSequence += cleanSequence.charAt(i - 1); } } } } // add the sequence to the formatted sequence if (i % 10 == 0) { if (previousAminoAcidWasCovered && !previousAminoAcidWasSelected) { sequenceTable += "<td><tt>" + currentCellSequence + "</tt></td>"; } else { sequenceTable += "<td><tt>" + currentCellSequence + "</span></tt></td>"; } currentCellSequence = ""; } previousAminoAcidWasCovered = coveredPeptide; } // add remaining tags and complete the formatted sequence sequenceTable += "<td><tt>" + currentCellSequence + "</tt></td></table>"; String formattedSequence = "<html><body><table cellspacing='2'>" + sequenceTable + "</html></body>"; // display the formatted sequence editorPane.setText(formattedSequence); // make sure that the currently selected peptide is visible if (selectedPeptideStart.size() > 0 && selectedPeptideStart.get(0) != -1) { boolean referenceMarkerFound = false; for (int i = 0; i < referenceMarkers.size() - 1 && !referenceMarkerFound; i++) { if (selectedPeptideStart.get(0) >= referenceMarkers.get(i) && selectedPeptideStart.get(0) < referenceMarkers.get(i + 1)) { final JEditorPane tempEditorPane = editorPane; final String referenceMarker = referenceMarkers.get(i).toString(); // invoke later to give time for components to update SwingUtilities.invokeLater(new Runnable() { public void run() { tempEditorPane.scrollToReference(referenceMarker); } }); referenceMarkerFound = true; } } if (!referenceMarkerFound) { final JEditorPane tempEditorPane = editorPane; final String referenceMarker = referenceMarkers.get(referenceMarkers.size() - 1).toString(); // invoke later to give time for components to update SwingUtilities.invokeLater(new Runnable() { public void run() { tempEditorPane.scrollToReference(referenceMarker); } }); } } else { editorPane.setCaretPosition(0); } return (sequenceCoverage / cleanSequence.length()) * 100; } /** * Formats the protein sequence such that both the covered parts of the sequence * and the peptide selected in the peptide table is highlighted. The result is * inserted into the provided JEditorPane. * * @param editorPane the editor pane to add the formatted sequence to * @param cleanSequence the clean protein sequence, i.e., just the amino acid sequence * @param selectedPeptideStart the start index of the currently selected peptide * @param selectedPeptideEnd the end index if the currently selected peptide * @param coverage the sequence coverage map with numbers indicating the number * of times a given residue is used, zero means no coverage, * (note: only uses index 1-n) * @param aKeyValuePairs the key value pairs used for PEFF formating * @param selectedAnnotationType the colors to use for the different tags, key is the tag * @return the calculated sequence coverage in percent (0-100) */ public static double formatProteinSequence(JEditorPane editorPane, String cleanSequence, int selectedPeptideStart, int selectedPeptideEnd, int[] coverage, TreeMap<String, String> aKeyValuePairs, HashMap<String, Color> selectedAnnotationType) { // @TODO: the html code ought to be optimized similar to the method above!! if (cleanSequence.length() != coverage.length - 1) { throw new IllegalArgumentException("The lenght of the coverage map has to be equal to the lenght of the sequence + 1!"); } peffKeyValuePairs = aKeyValuePairs; String sequenceTable = "", currentCellSequence = ""; boolean selectedPeptide = false, coveredPeptide = false; double sequenceCoverage = 0; // see how many amino acids we have room for FontMetrics fm = editorPane.getGraphics().getFontMetrics(); int fontWidth = fm.stringWidth("X"); // hardcoding needed to due issues with other look and feels if (!UIManager.getLookAndFeel().getName().equalsIgnoreCase("Nimbus")) { fontWidth = 8; // 8 is to represent the default average font width in html // @TODO: find a way of removing this hardcoding... } int indexWidth = 200; if (cleanSequence.length() > 999) { indexWidth += 50; } double temp = (editorPane.getParent().getWidth() - indexWidth) / fontWidth; int numberOfAminoAcidsPerRow = (int) temp / 10; numberOfAminoAcidsPerRow *= 10; ArrayList<Integer> referenceMarkers = new ArrayList<Integer>(); String[] modificationMap = new String[coverage.length]; String[] variantMap = new String[coverage.length]; String[] signalMap = new String[coverage.length]; String[] siteMap = new String[coverage.length]; if (peffKeyValuePairs.containsKey("ModRes") && selectedAnnotationType.containsKey("ModRes_Foreground")) { // @TODO: support ModResPsi!! String modifications = peffKeyValuePairs.get("ModRes"); modifications = modifications.substring(1, modifications.length() - 1); String[] mods = modifications.split("\\)\\("); for (int j = 0; j < mods.length; j++) { String[] tempMod = mods[j].split("\\|"); int index = new Integer(tempMod[0]); String psiMod = tempMod[1]; if (modificationMap[index] == null) { modificationMap[index] = "Modification(s):<br>" + index + ": " + psiMod; } else { modificationMap[index] += "<br>" + index + ": " + psiMod; } } for (int i = 0; i < modificationMap.length; i++) { if (modificationMap[i] != null) { modificationMap[i] = "<html>" + modificationMap[i] + "<html>"; } } } if (peffKeyValuePairs.containsKey("Variant") && selectedAnnotationType.containsKey("Variant_Foreground")) { fillTwoValueMap("Variant(s)", peffKeyValuePairs.get("Variant"), variantMap); } if (peffKeyValuePairs.containsKey("Signal") && selectedAnnotationType.containsKey("Signal_Foreground")) { fillTwoValueMap("Signal(s)", peffKeyValuePairs.get("Signal"), signalMap); } if (peffKeyValuePairs.containsKey("Site") && selectedAnnotationType.containsKey("Site_Foreground")) { fillTwoValueMap("Site(s)", peffKeyValuePairs.get("Site"), siteMap); } String uncoveredForegroundColor = Util.color2Hex(Color.lightGray); String uncoveredBackgroundColor = Util.color2Hex(Color.WHITE); String modsForegroundColor = null; String modsBackgroundColor = null; String variantsForegroundColor = null; String variantsBackgroundColor = null; String signalForegroundColor = null; String signalBackgroundColor = null; String siteForegroundColor = null; String siteBackgroundColor = null; String multipleForegroundColor = null; String multipleBackgroundColor = null; if (selectedAnnotationType.get("ModRes_Foreground") != null) { modsForegroundColor = Util.color2Hex(selectedAnnotationType.get("ModRes_Foreground")); modsBackgroundColor = Util.color2Hex(selectedAnnotationType.get("ModRes_Background")); } if (selectedAnnotationType.get("Variant_Foreground") != null) { variantsForegroundColor = Util.color2Hex(selectedAnnotationType.get("Variant_Foreground")); variantsBackgroundColor = Util.color2Hex(selectedAnnotationType.get("Variant_Background")); } if (selectedAnnotationType.get("Signal_Foreground") != null) { signalForegroundColor = Util.color2Hex(selectedAnnotationType.get("Signal_Foreground")); signalBackgroundColor = Util.color2Hex(selectedAnnotationType.get("Signal_Background")); } if (selectedAnnotationType.get("Site_Foreground") != null) { siteForegroundColor = Util.color2Hex(selectedAnnotationType.get("Site_Foreground")); siteBackgroundColor = Util.color2Hex(selectedAnnotationType.get("Site_Background")); } if (selectedAnnotationType.get("Multiple_Foreground") != null) { multipleForegroundColor = Util.color2Hex(selectedAnnotationType.get("Multiple_Foreground")); multipleBackgroundColor = Util.color2Hex(selectedAnnotationType.get("Multiple_Background")); } // iterate the coverage table and create the formatted sequence string for (int i = 1; i < coverage.length; i++) { // add residue number and line break if (i % numberOfAminoAcidsPerRow == 1 || i == 1) { sequenceTable += "</tr><tr><td><font color=black><a name=\"" + i + "\">" + i + "</a></font></td>"; referenceMarkers.add(i); } // check if the current residues is covered if (coverage[i] > 0) { sequenceCoverage++; coveredPeptide = true; } else { coveredPeptide = false; } // @TODO: make it possible to turn of the coverage highlighting again... // if (!showCoverage) { // coveredPeptide = true; // } // check if the current residue is contained in the selected peptide if (i == selectedPeptideStart) { selectedPeptide = true; } else if (i == selectedPeptideEnd + 1) { selectedPeptide = false; } int index = 0; if (modificationMap[i] != null) { index++; } if (variantMap[i] != null) { index++; } if (signalMap[i] != null) { index++; } if (siteMap[i] != null) { index++; } if (index == 0) { // no annotations if (coveredPeptide) { if (selectedPeptide) { currentCellSequence += "<font color=black>" + "<u>" + cleanSequence.charAt(i - 1) + "</u>" + "</font>"; } else { currentCellSequence += "<font color=black>" + cleanSequence.charAt(i - 1) + "</font>"; } } else { currentCellSequence += "<span style=\"color:#" + uncoveredForegroundColor + "; background:#" + uncoveredBackgroundColor + "\">" + cleanSequence.charAt(i - 1) + "</span>"; } } else if (index == 1) { // single annotation String currentForegroundColor = ""; String currentBackgroundColor = ""; String currentValue = null; if (modificationMap[i] != null) { currentForegroundColor = modsForegroundColor; currentBackgroundColor = modsBackgroundColor; currentValue = modificationMap[i]; } else if (variantMap[i] != null) { currentForegroundColor = variantsForegroundColor; currentBackgroundColor = variantsBackgroundColor; currentValue = variantMap[i]; } else if (signalMap[i] != null) { currentForegroundColor = signalForegroundColor; currentBackgroundColor = signalBackgroundColor; currentValue = signalMap[i]; } else if (siteMap[i] != null) { currentForegroundColor = siteForegroundColor; currentBackgroundColor = siteBackgroundColor; currentValue = siteMap[i]; } if (selectedPeptide) { currentCellSequence += "<span style=\"color:#" + currentForegroundColor + ";background:#" + currentBackgroundColor + "\">" + "<A HREF=\"" + currentValue + "\" TITLE=\"" + currentValue + "\">" + cleanSequence.charAt(i - 1) + "</A></span>"; } else { if (coveredPeptide) { currentCellSequence += "<span style=\"color:#" + currentForegroundColor + ";background:#" + currentBackgroundColor + ";text-decoration:none\">" + "<A HREF=\"" + currentValue + "\" TITLE=\"" + currentValue + "\">" + cleanSequence.charAt(i - 1) + "</A></span>"; } else { currentCellSequence += "<span style=\"color:#" + uncoveredForegroundColor + ";background:#" + currentBackgroundColor + ";text-decoration:none\">" + "<A HREF=\"" + currentValue + "\" TITLE=\"" + currentValue + "\">" + cleanSequence.charAt(i - 1) + "</A></span>"; } } } else { // multiple annotations String tempAnnotation = ""; if (modificationMap[i] != null) { tempAnnotation += modificationMap[i]; } if (variantMap[i] != null) { if (tempAnnotation.length() > 0) { tempAnnotation += "<br><br>" + variantMap[i]; } else { tempAnnotation += variantMap[i]; } } if (signalMap[i] != null) { if (tempAnnotation.length() > 0) { tempAnnotation += "<br><br>" + signalMap[i]; } else { tempAnnotation += signalMap[i]; } } if (siteMap[i] != null) { if (tempAnnotation.length() > 0) { tempAnnotation += "<br><br>" + siteMap[i]; } else { tempAnnotation += siteMap[i]; } } if (selectedPeptide) { currentCellSequence += "<span style=\"color:#" + multipleForegroundColor + ";background:#" + multipleBackgroundColor + "\"><b>" + "<A HREF=\"" + tempAnnotation + "\" TITLE=\"" + tempAnnotation + "\">" + cleanSequence.charAt(i - 1) + "</A></b></span>"; } else { if (coveredPeptide) { currentCellSequence += "<span style=\"color:#" + multipleForegroundColor + ";background:#" + multipleBackgroundColor + ";text-decoration:none\"><b>" + "<A HREF=\"" + tempAnnotation + "\" TITLE=\"" + tempAnnotation + "\">" + cleanSequence.charAt(i - 1) + "</A></b></span>"; } else { currentCellSequence += "<span style=\"color:#" + uncoveredForegroundColor + ";background:#" + multipleBackgroundColor + ";text-decoration:none\"><b>" + "<A HREF=\"" + tempAnnotation + "\" TITLE=\"" + tempAnnotation + "\">" + cleanSequence.charAt(i - 1) + "</A></b></span>"; } } } // add the sequence to the formatted sequence if (i % 10 == 0) { sequenceTable += "<td><tt>" + currentCellSequence + "</tt></td>"; currentCellSequence = ""; } } // add remaining tags and complete the formatted sequence sequenceTable += "<td><tt>" + currentCellSequence + "</tt></td></table><font color=black>"; String formattedSequence = "<html><body><table cellspacing='2'>" + sequenceTable + "</html></body>"; // display the formatted sequence editorPane.setText(formattedSequence); // make sure that the currently selected peptide is visible if (selectedPeptideStart != -1) { boolean referenceMarkerFound = false; for (int i = 0; i < referenceMarkers.size() - 1 && !referenceMarkerFound; i++) { if (selectedPeptideStart >= referenceMarkers.get(i) && selectedPeptideStart < referenceMarkers.get(i + 1)) { final JEditorPane tempEditorPane = editorPane; final String referenceMarker = referenceMarkers.get(i).toString(); // invoke later to give time for components to update SwingUtilities.invokeLater(new Runnable() { public void run() { tempEditorPane.scrollToReference(referenceMarker); } }); referenceMarkerFound = true; } } if (!referenceMarkerFound) { final JEditorPane tempEditorPane = editorPane; final String referenceMarker = referenceMarkers.get(referenceMarkers.size() - 1).toString(); // invoke later to give time for components to update SwingUtilities.invokeLater(new Runnable() { public void run() { tempEditorPane.scrollToReference(referenceMarker); } }); } } else { editorPane.setCaretPosition(0); } return (sequenceCoverage / cleanSequence.length()) * 100; } private static void fillTwoValueMap(String type, String values, String[] map) { values = values.substring(1, values.length() - 1); String[] value = values.split("\\)\\("); for (int j = 0; j < value.length; j++) { String[] tempValue = value[j].split("\\|"); int start = new Integer(tempValue[0]); int end = new Integer(tempValue[1]); String sequence = ""; if (tempValue.length > 2) { sequence = tempValue[2]; } if (start != end) { for (int k = start; k <= end; k++) { if (map[k] == null) { map[k] = type + ":<br>" + start + "-" + end + ": " + sequence; } else { map[k] += "<br>" + start + "-" + end + ": " + sequence; } } } else { if (map[start] == null) { map[start] = type + ":<br>" + start + ": " + sequence; } else { map[start] += "<br>" + start + ": " + sequence; } } } for (int i = 0; i < map.length; i++) { if (map[i] != null) { map[i] = "<html>" + map[i] + "<html>"; } } } }