X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;ds=sidebyside;f=src%2Fjalview%2Fgui%2FPopupMenu.java;h=568f7f1fe28e71fa66236d04cd9e51b9a72691f4;hb=f10fd0d2963d13937cda4f44c6216003a8472696;hp=06e35cd2f3025941410b359c456d870a017edb03;hpb=4eb8d7a0f14e5dd1dc5ad5ad0960bae9b25fa043;p=jalview.git diff --git a/src/jalview/gui/PopupMenu.java b/src/jalview/gui/PopupMenu.java index 06e35cd..568f7f1 100644 --- a/src/jalview/gui/PopupMenu.java +++ b/src/jalview/gui/PopupMenu.java @@ -20,10 +20,36 @@ */ 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; @@ -32,6 +58,7 @@ import jalview.datamodel.AlignmentAnnotation; 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; @@ -46,35 +73,13 @@ import jalview.schemes.ColourSchemeI; 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 @@ -83,6 +88,11 @@ import javax.swing.JRadioButtonMenuItem; 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; @@ -344,29 +354,33 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener * 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 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 features, List groupLinks) + List groupLinks) { - this(true, alignPanel, seq, features, groupLinks); + this(true, alignPanel, seq, -1, groupLinks); } /** @@ -376,14 +390,15 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener * @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 features, - List groupLinks) + final SequenceI seq, final int column, List groupLinks) { + Objects.requireNonNull(seq); this.forIdPanel = fromIdPanel; this.ap = alignPanel; sequence = seq; @@ -409,7 +424,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener * 'reference annotations' that may be added to the alignment. First for the * currently selected sequence (if there is one): */ - final List selectedSequence = (forIdPanel + final List selectedSequence = (forIdPanel && seq != null ? Arrays.asList(seq) : Collections. emptyList()); buildAnnotationTypesMenus(seqShowAnnotationsMenu, @@ -701,87 +716,183 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener rnaStructureMenu.setVisible(false); } + addLinksAndFeatures(seq, column); + } + + /** + * Adds + *
    + *
  • configured sequence database links (ID panel popup menu)
  • + *
  • non-positional feature links (ID panel popup menu)
  • + *
  • positional feature links (alignment panel popup menu)
  • + *
  • feature details links (alignment panel popup menu)
  • + *
+ * 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 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 features) + protected void addFeatureDetails(List 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); @@ -1665,7 +1776,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener "label.create_sequence_details_report_annotation_for", new Object[] { seq.getDisplayId(true) }) + "

"); - new SequenceAnnotationReport(null).createSequenceAnnotationReport( + new SequenceAnnotationReport(false).createSequenceAnnotationReport( contents, seq, true, true, ap.getSeqPanel().seqCanvas.fr); contents.append("

"); }