import jalview.analysis.AlignmentAnnotationUtils;
import jalview.analysis.AlignmentUtils;
import jalview.analysis.Conservation;
+import jalview.api.AlignViewportI;
import jalview.bin.Cache;
import jalview.commands.ChangeCaseCommand;
import jalview.commands.EditCommand;
import jalview.datamodel.AlignmentI;
import jalview.datamodel.DBRefEntry;
import jalview.datamodel.HiddenColumns;
+import jalview.datamodel.MappedFeatures;
import jalview.datamodel.PDBEntry;
import jalview.datamodel.SequenceFeature;
import jalview.datamodel.SequenceGroup;
import jalview.schemes.ColourSchemes;
import jalview.schemes.PIDColourScheme;
import jalview.schemes.ResidueColourScheme;
+import jalview.util.Comparison;
import jalview.util.GroupUrlLink;
import jalview.util.GroupUrlLink.UrlStringTooLongException;
import jalview.util.MessageManager;
import jalview.util.StringUtils;
import jalview.util.UrlLink;
+import jalview.viewmodel.seqfeatures.FeatureRendererModel;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.Vector;
* Constructor for a PopupMenu for a click in the alignment panel (on a residue)
*
* @param ap
+ * the panel in which the mouse is clicked
* @param seq
- * @param features
- * sequence features overlapping the clicked residue
+ * the sequence under the mouse
+ * @throws NullPointerException
+ * if seq is null
*/
- public PopupMenu(final AlignmentPanel ap, SequenceI seq,
- List<SequenceFeature> features)
+ public PopupMenu(final AlignmentPanel ap, SequenceI seq, int column)
{
- this(false, ap, seq, features, null);
+ this(false, ap, seq, column, null);
}
/**
* Constructor for a PopupMenu for a click in the sequence id panel
*
* @param alignPanel
+ * the panel in which the mouse is clicked
* @param seq
- * @param features
- * non-positional features for the sequence
+ * the sequence under the mouse click
* @param groupLinks
+ * templates for sequence external links
+ * @throws NullPointerException
+ * if seq is null
*/
public PopupMenu(final AlignmentPanel alignPanel, final SequenceI seq,
- List<SequenceFeature> features, List<String> groupLinks)
+ List<String> groupLinks)
{
- this(true, alignPanel, seq, features, groupLinks);
+ this(true, alignPanel, seq, -1, groupLinks);
}
/**
* @param fromIdPanel
* @param alignPanel
* @param seq
- * @param features
+ * @param column
+ * aligned column position (0...)
* @param groupLinks
*/
private PopupMenu(boolean fromIdPanel,
final AlignmentPanel alignPanel,
- final SequenceI seq, List<SequenceFeature> features,
- List<String> groupLinks)
+ final SequenceI seq, final int column, List<String> groupLinks)
{
+ Objects.requireNonNull(seq);
this.forIdPanel = fromIdPanel;
this.ap = alignPanel;
sequence = seq;
* 'reference annotations' that may be added to the alignment. First for the
* currently selected sequence (if there is one):
*/
- final List<SequenceI> selectedSequence = (forIdPanel
+ final List<SequenceI> selectedSequence = (forIdPanel && seq != null
? Arrays.asList(seq)
: Collections.<SequenceI> emptyList());
buildAnnotationTypesMenus(seqShowAnnotationsMenu,
rnaStructureMenu.setVisible(false);
}
+ addLinksAndFeatures(seq, column);
+ }
+
+ /**
+ * Adds
+ * <ul>
+ * <li>configured sequence database links (ID panel popup menu)</li>
+ * <li>non-positional feature links (ID panel popup menu)</li>
+ * <li>positional feature links (alignment panel popup menu)</li>
+ * <li>feature details links (alignment panel popup menu)</li>
+ * </ul>
+ * If this panel is also showed complementary (CDS/protein) features, then links
+ * to their feature details are also added.
+ *
+ * @param seq
+ * @param column
+ */
+ void addLinksAndFeatures(final SequenceI seq, final int column)
+ {
+ List<SequenceFeature> features = null;
+ if (forIdPanel)
+ {
+ features = sequence.getFeatures().getNonPositionalFeatures();
+ }
+ else
+ {
+ features = ap.getFeatureRenderer().findFeaturesAtColumn(sequence,
+ column + 1);
+ }
+
addLinks(seq, features);
if (!forIdPanel)
{
- addFeatureDetails(features);
+ addFeatureDetails(features, seq, column);
}
}
* Add a link to show feature details for each sequence feature
*
* @param features
+ * @param column
+ * @param seq
*/
- protected void addFeatureDetails(List<SequenceFeature> features)
+ protected void addFeatureDetails(List<SequenceFeature> features,
+ SequenceI seq, int column)
{
- if (features == null || features.isEmpty())
+ /*
+ * add features in CDS/protein complement at the corresponding
+ * position if configured to do so
+ */
+ MappedFeatures mf = null;
+ if (ap.av.isShowComplementFeatures())
+ {
+ if (!Comparison.isGap(sequence.getCharAt(column)))
+ {
+ AlignViewportI complement = ap.getAlignViewport()
+ .getCodingComplement();
+ AlignFrame af = Desktop.getAlignFrameFor(complement);
+ FeatureRendererModel fr2 = af.getFeatureRenderer();
+ int seqPos = sequence.findPosition(column);
+ mf = fr2.findComplementFeaturesAtResidue(sequence, seqPos);
+ }
+ }
+
+ if (features.isEmpty() && mf == null)
{
+ /*
+ * no features to show at this position
+ */
return;
}
+
JMenu details = new JMenu(
MessageManager.getString("label.feature_details"));
add(details);
+ String name = seq.getName();
for (final SequenceFeature sf : features)
{
- int start = sf.getBegin();
- int end = sf.getEnd();
- String desc = null;
- if (start == end)
+ addFeatureDetailsMenuItem(details, name, sf);
+ }
+
+ if (mf != null)
+ {
+ name = mf.fromSeq == seq ? mf.mapping.getTo().getName()
+ : mf.fromSeq.getName();
+ for (final SequenceFeature sf : mf.features)
{
- desc = String.format("%s %d", sf.getType(), start);
+ addFeatureDetailsMenuItem(details, name, sf);
}
- else
+ }
+ }
+
+ /**
+ * A helper method to add one menu item whose action is to show details for one
+ * feature
+ *
+ * @param details
+ * @param seqName
+ * @param sf
+ */
+ void addFeatureDetailsMenuItem(JMenu details, final String seqName,
+ final SequenceFeature sf)
+ {
+ int start = sf.getBegin();
+ int end = sf.getEnd();
+ String desc = null;
+ if (start == end)
+ {
+ desc = String.format("%s %d", sf.getType(), start);
+ }
+ else
+ {
+ desc = String.format("%s %d-%d", sf.getType(), start, end);
+ }
+ String tooltip = desc;
+ String description = sf.getDescription();
+ if (description != null)
+ {
+ description = StringUtils.stripHtmlTags(description);
+ if (description.length() > 12)
{
- desc = String.format("%s %d-%d", sf.getType(), start, end);
+ desc = desc + " " + description.substring(0, 12) + "..";
}
- String tooltip = desc;
- String description = sf.getDescription();
- if (description != null)
+ else
{
- description = StringUtils.stripHtmlTags(description);
- if (description.length() > 12)
- {
- desc = desc + " " + description.substring(0, 12) + "..";
- }
- else
- {
- desc = desc + " " + description;
- }
- tooltip = tooltip + " " + description;
+ desc = desc + " " + description;
}
- if (sf.getFeatureGroup() != null)
+ tooltip = tooltip + " " + description;
+ }
+ if (sf.getFeatureGroup() != null)
+ {
+ tooltip = tooltip + (" (" + sf.getFeatureGroup() + ")");
+ }
+ JMenuItem item = new JMenuItem(desc);
+ item.setToolTipText(tooltip);
+ item.addActionListener(new ActionListener()
+ {
+ @Override
+ public void actionPerformed(ActionEvent e)
{
- tooltip = tooltip + (" (" + sf.getFeatureGroup() + ")");
+ showFeatureDetails(seqName, sf);
}
- JMenuItem item = new JMenuItem(desc);
- item.setToolTipText(tooltip);
- item.addActionListener(new ActionListener()
- {
- @Override
- public void actionPerformed(ActionEvent e)
- {
- showFeatureDetails(sf);
- }
- });
- details.add(item);
- }
+ });
+ details.add(item);
}
/**
* Opens a panel showing a text report of feature dteails
*
+ * @param seqName
+ *
* @param sf
*/
- protected void showFeatureDetails(SequenceFeature sf)
+ protected void showFeatureDetails(String seqName, SequenceFeature sf)
{
CutAndPasteHtmlTransfer cap = new CutAndPasteHtmlTransfer();
// it appears Java's CSS does not support border-collapse :-(
cap.addStylesheetRule("table { border-collapse: collapse;}");
cap.addStylesheetRule("table, td, th {border: 1px solid black;}");
- cap.setText(sf.getDetailsReport(sequence));
+ cap.setText(sf.getDetailsReport(seqName));
Desktop.addInternalFrame(cap,
MessageManager.getString("label.feature_details"), 500, 500);
public void testGetDetailsReport()
{
SequenceI seq = new Sequence("TestSeq", "PLRFQMD");
+ String seqName = seq.getName();
// single locus, no group, no score
SequenceFeature sf = new SequenceFeature("variant", "G,C", 22, 22, null);
String expected = "<br><table><tr><td>Location</td><td>TestSeq</td><td>22</td></tr>"
+ "<tr><td>Type</td><td>variant</td><td></td></tr>"
+ "<tr><td>Description</td><td>G,C</td><td></td></tr></table>";
- assertEquals(expected, sf.getDetailsReport(seq));
+ assertEquals(expected, sf.getDetailsReport(seqName));
// contact feature
sf = new SequenceFeature("Disulphide Bond", "a description", 28, 31,
expected = "<br><table><tr><td>Location</td><td>TestSeq</td><td>28:31</td></tr>"
+ "<tr><td>Type</td><td>Disulphide Bond</td><td></td></tr>"
+ "<tr><td>Description</td><td>a description</td><td></td></tr></table>";
- assertEquals(expected, sf.getDetailsReport(seq));
+ assertEquals(expected, sf.getDetailsReport(seqName));
sf = new SequenceFeature("variant", "G,C", 22, 33,
12.5f, "group");
+ "<tr><td>Group</td><td>group</td><td></td></tr>"
+ "<tr><td>Child</td><td></td><td>ENSP002</td></tr>"
+ "<tr><td>Parent</td><td></td><td>ENSG001</td></tr></table>";
- assertEquals(expected, sf.getDetailsReport(seq));
+ assertEquals(expected, sf.getDetailsReport(seqName));
/*
* feature with embedded html link in description
+ "<tr><td>Type</td><td>Pfam</td><td></td></tr>"
+ "<tr><td>Description</td><td>Fer2 Status: True Positive <a href=\"http://pfam.xfam.org/family/PF00111\">Pfam 8_8</a></td><td></td></tr>"
+ "<tr><td>Group</td><td>Uniprot</td><td></td></tr></table>";
- assertEquals(expected, sf.getDetailsReport(seq));
+ assertEquals(expected, sf.getDetailsReport(seqName));
}
}