*/
package jalview.gui;
+import java.awt.Color;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Hashtable;
+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;
+
+import javax.swing.ButtonGroup;
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JColorChooser;
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+import javax.swing.JPopupMenu;
+import javax.swing.JRadioButtonMenuItem;
+
import jalview.analysis.AAFrequency;
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 java.awt.Color;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.BitSet;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Hashtable;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.SortedMap;
-import java.util.TreeMap;
-import java.util.Vector;
-
-import javax.swing.ButtonGroup;
-import javax.swing.JCheckBoxMenuItem;
-import javax.swing.JColorChooser;
-import javax.swing.JMenu;
-import javax.swing.JMenuItem;
-import javax.swing.JPopupMenu;
-import javax.swing.JRadioButtonMenuItem;
+import jalview.viewmodel.seqfeatures.FeatureRendererModel;
/**
* The popup menu that is displayed on right-click on a sequence id, or in the
public class PopupMenu extends JPopupMenu implements ColourChangeListener
{
/*
+ * maximum length of feature description to include in popup menu item text
+ */
+ private static final int FEATURE_DESC_MAX = 40;
+
+ /*
* true for ID Panel menu, false for alignment panel menu
*/
private final boolean forIdPanel;
* 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
+ * Add a menu item to show feature details for each sequence feature. Any
+ * linked 'virtual' features (CDS/protein) are also optionally found and
+ * included.
*
* @param features
+ * @param seq
+ * @param column
*/
- protected void addFeatureDetails(List<SequenceFeature> features)
+ protected void addFeatureDetails(List<SequenceFeature> features,
+ final SequenceI seq, final 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)
- {
- desc = String.format("%s %d", sf.getType(), start);
- }
- else
+ addFeatureDetailsMenuItem(details, name, sf, null);
+ }
+
+ if (mf != null)
+ {
+ for (final SequenceFeature sf : mf.features)
{
- desc = String.format("%s %d-%d", sf.getType(), start, end);
+ addFeatureDetailsMenuItem(details, name, sf, mf);
}
- String tooltip = desc;
- String description = sf.getDescription();
- if (description != null)
+ }
+ }
+
+ /**
+ * A helper method to add one menu item whose action is to show details for
+ * one feature. The menu text includes feature description, but this may be
+ * truncated.
+ *
+ * @param details
+ * @param seqName
+ * @param sf
+ * @param mf
+ */
+ void addFeatureDetailsMenuItem(JMenu details, final String seqName,
+ final SequenceFeature sf, MappedFeatures mf)
+ {
+ int start = sf.getBegin();
+ int end = sf.getEnd();
+ if (mf != null)
+ {
+ /*
+ * show local rather than linked feature coordinates
+ */
+ int[] beginRange = mf.getMappedPositions(start, start);
+ start = beginRange[0];
+ int[] endRange = mf.getMappedPositions(end, end);
+ end = endRange[endRange.length - 1];
+ }
+ StringBuilder desc = new StringBuilder();
+ desc.append(sf.getType()).append(" ").append(String.valueOf(start));
+ if (start != end)
+ {
+ desc.append(sf.isContactFeature() ? ":" : "-");
+ desc.append(String.valueOf(end));
+ }
+ String description = sf.getDescription();
+ if (description != null)
+ {
+ desc.append(" ");
+ description = StringUtils.stripHtmlTags(description);
+
+ /*
+ * truncate overlong descriptions unless they contain an href
+ * (as truncation could leave corrupted html)
+ */
+ boolean hasLink = description.indexOf("a href") > -1;
+ if (description.length() > FEATURE_DESC_MAX && !hasLink)
{
- description = StringUtils.stripHtmlTags(description);
- if (description.length() > 12)
- {
- desc = desc + " " + description.substring(0, 12) + "..";
- }
- else
- {
- desc = desc + " " + description;
- }
- tooltip = tooltip + " " + description;
+ description = description.substring(0, FEATURE_DESC_MAX) + "...";
}
- if (sf.getFeatureGroup() != null)
+ desc.append(description);
+ }
+ String featureGroup = sf.getFeatureGroup();
+ if (featureGroup != null)
+ {
+ desc.append(" (").append(featureGroup).append(")");
+ }
+ String htmlText = JvSwingUtils.wrapTooltip(true, desc.toString());
+ JMenuItem item = new JMenuItem(htmlText);
+ item.addActionListener(new ActionListener()
+ {
+ @Override
+ public void actionPerformed(ActionEvent e)
{
- tooltip = tooltip + (" (" + sf.getFeatureGroup() + ")");
+ showFeatureDetails(sf, seqName, mf);
}
- 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
+ * Opens a panel showing a text report of feature details
*
* @param sf
+ * @param seqName
+ * @param mf
*/
- protected void showFeatureDetails(SequenceFeature sf)
+ protected void showFeatureDetails(SequenceFeature sf, String seqName,
+ MappedFeatures mf)
{
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, mf));
Desktop.addInternalFrame(cap,
MessageManager.getString("label.feature_details"), 500, 500);
"label.create_sequence_details_report_annotation_for",
new Object[]
{ seq.getDisplayId(true) }) + "</h2></p><p>");
- new SequenceAnnotationReport(null).createSequenceAnnotationReport(
+ new SequenceAnnotationReport(false).createSequenceAnnotationReport(
contents, seq, true, true, ap.getSeqPanel().seqCanvas.fr);
contents.append("</p>");
}