package jalview.gui;
import jalview.analysis.AAFrequency;
+import jalview.analysis.AlignmentAnnotationUtils;
import jalview.analysis.Conservation;
import jalview.commands.ChangeCaseCommand;
import jalview.commands.EditCommand;
import jalview.datamodel.SequenceI;
import jalview.io.FormatAdapter;
import jalview.io.SequenceAnnotationReport;
-import jalview.renderer.AnnotationRenderer;
import jalview.schemes.AnnotationColourGradient;
import jalview.schemes.Blosum62ColourScheme;
import jalview.schemes.BuriedColourScheme;
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.HashMap;
import java.util.Hashtable;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.TreeMap;
import java.util.Vector;
import javax.swing.ButtonGroup;
JMenu hideAnnotationsMenu = new JMenu();
+ JMenuItem addReferenceAnnotations = new JMenuItem();
+
JMenuItem sequenceFeature = new JMenuItem();
JMenuItem textColour = new JMenuItem();
}
/*
- * Build menus for annotation types that may be shown or hidden.
+ * Build menus for annotation types that may be shown or hidden, and for
+ * 'reference annotations' that may be added to the alignment. The scope is
+ * annotations for the current selection group (if there is one), else the
+ * current sequence (if there is one), else not applicable (e.g. for popup
+ * menu within the sequence).
*/
- buildAnnotationTypesMenus();
+ final List<SequenceI> sequenceScope = getSequenceScope(seq);
+ if (!sequenceScope.isEmpty())
+ {
+ buildAnnotationTypesMenus(sequenceScope);
+ configureReferenceAnnotationsMenu(addReferenceAnnotations, sequenceScope);
+ }
try
{
menuItem = new JMenuItem();
menuItem.setText(pdb.getId());
- menuItem.addActionListener(new java.awt.event.ActionListener()
+ menuItem.addActionListener(new ActionListener()
{
+ @Override
public void actionPerformed(ActionEvent e)
{
// TODO re JAL-860: optionally open dialog or provide a menu entry
"label.2d_rna_structure_line", new String[]
{ structureLine }));
menuItem.addActionListener(new java.awt.event.ActionListener()
-
{
@Override
public void actionPerformed(ActionEvent e)
/**
* Add annotation types to a 'Show annotations' or 'Hide annotations' menu.
* "All" is added first, followed by a separator. Then add any annotation
- * types associated with the current selection. The second parameter controls
- * whether we include only currently visible annotation types (for the Hide
- * menu), or only currently hidden annotation types (for the Show menu).
+ * types associated with the current selection.
* <p>
* Some annotation rows are always rendered together - these can be identified
* by a common graphGroup property > -1. Only one of each group will be marked
* composite type name, e.g.
* <p>
* IUPredWS (Long), IUPredWS (Short)
+ *
+ * @param forSequences
*/
- protected void buildAnnotationTypesMenus()
+ protected void buildAnnotationTypesMenus(List<SequenceI> forSequences)
{
showAnnotationsMenu.removeAll();
hideAnnotationsMenu.removeAll();
final List<String> all = Arrays.asList(ALL_ANNOTATIONS);
- addAnnotationTypeToShowHide(showAnnotationsMenu,
- all, true,
- true);
- addAnnotationTypeToShowHide(hideAnnotationsMenu, all, true,
- false);
+ addAnnotationTypeToShowHide(showAnnotationsMenu, forSequences, "", all,
+ true, true);
+ addAnnotationTypeToShowHide(hideAnnotationsMenu, forSequences, "", all,
+ true, false);
showAnnotationsMenu.addSeparator();
hideAnnotationsMenu.addSeparator();
final AlignmentAnnotation[] annotations = ap.getAlignment()
.getAlignmentAnnotation();
- BitSet visibleGraphGroups = PopupMenu
- .getVisibleLineGraphGroups(annotations);
-
- List<List<String>> shownTypes = new ArrayList<List<String>>();
- List<List<String>> hiddenTypes = new ArrayList<List<String>>();
- PopupMenu.getAnnotationTypesForShowHide(shownTypes, hiddenTypes,
- visibleGraphGroups, annotations, ap.av.getSelectionGroup());
-
- for (List<String> types : hiddenTypes)
- {
- addAnnotationTypeToShowHide(showAnnotationsMenu, types, false, true);
- }
-
- for (List<String> types : shownTypes)
- {
- addAnnotationTypeToShowHide(hideAnnotationsMenu, types, false, false);
- }
- }
-
- /**
- * Helper method to populate lists of annotation types for the Show/Hide
- * Annotations menus. If sequenceGroup is not null, this is restricted to
- * annotations which are associated with sequences in the selection group.
- * <p/>
- * If an annotation row is currently visible, its type (label) is added (once
- * only per type), to the shownTypes list. If it is currently hidden, it is
- * added to the hiddenTypesList.
- * <p/>
- * For rows that belong to a line graph group, so are always rendered
- * together:
- * <ul>
- * <li>Treat all rows in the group as visible, if at least one of them is</li>
- * <li>Build a comma-separated label with all the types that belong to the
- * group</li>
- * </ul>
- *
- * @param shownTypes
- * @param hiddenTypes
- * @param visibleGraphGroups
- * @param annotations
- * @param sequenceGroup
- */
- public static void getAnnotationTypesForShowHide(
- List<List<String>> shownTypes, List<List<String>> hiddenTypes,
- BitSet visibleGraphGroups,
- AlignmentAnnotation[] annotations, SequenceGroup sequenceGroup)
- {
- // lookup table, key = graph group, value = list of types in the group
- Map<Integer, List<String>> groupLabels = new LinkedHashMap<Integer, List<String>>();
- List<String> addedToShown = new ArrayList<String>();
- List<String> addedToHidden = new ArrayList<String>();
+ /*
+ * Find shown/hidden annotations types, distinguished by source (calcId),
+ * and grouped by graphGroup.
+ */
+ Map<String, List<List<String>>> shownTypes = new HashMap<String, List<List<String>>>();
+ Map<String, List<List<String>>> hiddenTypes = new HashMap<String, List<List<String>>>();
+ AlignmentAnnotationUtils.getShownHiddenTypes(shownTypes,
+ hiddenTypes,
+ AlignmentAnnotationUtils.asList(annotations),
+ forSequences);
- for (AlignmentAnnotation aa : annotations)
+ for (String calcId : hiddenTypes.keySet())
{
-
- if (sequenceGroup == null
- || (aa.sequenceRef != null && sequenceGroup.getSequences()
- .contains(aa.sequenceRef)))
+ for (List<String> type : hiddenTypes.get(calcId))
{
- /*
- * Build a 'composite label' for types in line graph groups.
- */
- final List<String> labelAsList = new ArrayList<String>();
- labelAsList.add(aa.label);
- if (aa.graph == AlignmentAnnotation.LINE_GRAPH
- && aa.graphGroup > -1)
- {
- if (groupLabels.containsKey(aa.graphGroup))
- {
- if (!groupLabels.get(aa.graphGroup).contains(aa.label))
- {
- groupLabels.get(aa.graphGroup).add(aa.label);
- }
- }
- else
- {
- groupLabels.put(aa.graphGroup, labelAsList);
- }
- }
- else if (aa.visible && !addedToShown.contains(aa.label))
- {
- shownTypes.add(labelAsList);
- addedToShown.add(aa.label);
- }
- else
- {
- if (!aa.visible && !addedToHidden.contains(aa.label))
- {
- hiddenTypes.add(labelAsList);
- addedToHidden.add(aa.label);
- }
- }
+ addAnnotationTypeToShowHide(showAnnotationsMenu, forSequences,
+ calcId, type, false, true);
}
}
- /*
- * finally add the 'composite group labels' to the appropriate lists,
- * depending on whether the group is identified as visible or hidden
- */
- for (int group : groupLabels.keySet())
+ // grey out 'show annotations' if none are hidden
+ showAnnotationsMenu.setEnabled(!hiddenTypes.isEmpty());
+
+ for (String calcId : shownTypes.keySet())
{
- final List<String> groupLabel = groupLabels.get(group);
- if (visibleGraphGroups.get(group))
- {
- if (!shownTypes.contains(groupLabel))
- {
- shownTypes.add(groupLabel);
- }
- }
- else if (!hiddenTypes.contains(groupLabel))
+ for (List<String> type : shownTypes.get(calcId))
{
- hiddenTypes.add(groupLabel);
+ addAnnotationTypeToShowHide(hideAnnotationsMenu, forSequences,
+ calcId, type, false, false);
}
}
+ // grey out 'hide annotations' if none are shown
+ hideAnnotationsMenu.setEnabled(!shownTypes.isEmpty());
}
/**
- * Returns a BitSet (possibly empty) of those graphGroups for line graph
- * annotations, which have at least one member annotation row marked visible.
- * The logic is that only one row in the group is marked visible, but when it
- * is drawn, so are all the other rows in the same group.
- * <p/>
- * This lookup set allows us to check whether rows marked not visible are in
- * fact shown.
+ * Returns a list of sequences - either the current selection group (if there
+ * is one), else the specified single sequence.
*
- * @see AnnotationRenderer#drawComponent
- * @param annotations
+ * @param seq
* @return
*/
- public static BitSet getVisibleLineGraphGroups(
- AlignmentAnnotation[] annotations)
+ protected List<SequenceI> getSequenceScope(SequenceI seq)
{
- // todo move to a utility class
- BitSet result = new BitSet();
- for (AlignmentAnnotation ann : annotations)
+ List<SequenceI> forSequences = null;
+ final SequenceGroup selectionGroup = ap.av.getSelectionGroup();
+ if (selectionGroup != null && selectionGroup.getSize() > 0)
{
- if (ann.graph == AlignmentAnnotation.LINE_GRAPH && ann.visible)
- {
- int gg = ann.graphGroup;
- if (gg > -1)
- {
- result.set(gg);
- }
- }
+ forSequences = selectionGroup.getSequences();
}
- return result;
+ else
+ {
+ forSequences = seq == null ? Collections.<SequenceI> emptyList()
+ : Arrays.asList(seq);
+ }
+ return forSequences;
}
/**
*
* @param showOrHideMenu
* the menu to add to
+ * @param forSequences
+ * the sequences whose annotations may be shown or hidden
+ * @param calcId
* @param types
* the label to add
* @param allTypes
* type, else hide
*/
protected void addAnnotationTypeToShowHide(JMenu showOrHideMenu,
- final Collection<String> types, final boolean allTypes,
+ final List<SequenceI> forSequences, String calcId,
+ final List<String> types, final boolean allTypes,
final boolean actionIsShow)
{
String label = types.toString(); // [a, b, c]
label = label.substring(1, label.length() - 1);
final JMenuItem item = new JMenuItem(label);
+ item.setToolTipText(calcId);
item.addActionListener(new java.awt.event.ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
- showHideAnnotation_actionPerformed(types, allTypes, actionIsShow);
+ showHideAnnotation_actionPerformed(types, forSequences, allTypes,
+ actionIsShow);
}
});
showOrHideMenu.add(item);
/**
* Action on selecting a list of annotation type (or the 'all types' values)
- * to show or hide for the selection.
+ * to show or hide for the specified sequences.
*
* @param types
+ * @param forSequences
* @param anyType
* @param doShow
*/
protected void showHideAnnotation_actionPerformed(
- Collection<String> types,
+ Collection<String> types, List<SequenceI> forSequences,
boolean anyType, boolean doShow)
{
for (AlignmentAnnotation aa : ap.getAlignment()
if (anyType || types.contains(aa.label))
{
if ((aa.sequenceRef != null)
- && ap.av.getSelectionGroup().getSequences()
- .contains(aa.sequenceRef))
+ && forSequences.contains(aa.sequenceRef))
{
aa.visible = doShow;
}
add(groupMenu);
add(sequenceMenu);
this.add(structureMenu);
+ // annotations configuration panel suppressed for now
// groupMenu.add(chooseAnnotations);
- groupMenu.add(showAnnotationsMenu);
- groupMenu.add(hideAnnotationsMenu);
+
+ /*
+ * Add show/hide annotations to either Selection menu (if a selection group
+ * in force), else to the Sequence menu.
+ */
+ SequenceGroup sg = this.ap.av.getSelectionGroup();
+ if (sg != null && sg.getSize() > 0)
+ {
+ groupMenu.add(showAnnotationsMenu);
+ groupMenu.add(hideAnnotationsMenu);
+ groupMenu.add(addReferenceAnnotations);
+ }
+ else
+ {
+ sequenceMenu.add(showAnnotationsMenu);
+ sequenceMenu.add(hideAnnotationsMenu);
+ sequenceMenu.add(addReferenceAnnotations);
+ }
groupMenu.add(editMenu);
groupMenu.add(outputMenu);
groupMenu.add(sequenceFeature);
});
}
+ /**
+ * Check for any annotations on the underlying dataset sequences (for the
+ * current selection group) which are not on the alignment annotations for the
+ * sequence. If any are found, enable the option to add them to the alignment.
+ * The criteria for 'on the alignment' is finding an alignment annotation on
+ * the sequence, that matches on calcId and label. A tooltip is also
+ * constructed that displays the source (calcId) and type (label) of the
+ * annotations that can be added.
+ *
+ * @param menuItem
+ * @param forSequences
+ */
+ protected void configureReferenceAnnotationsMenu(
+ JMenuItem menuItem, List<SequenceI> forSequences)
+ {
+ menuItem.setText(MessageManager
+ .getString("label.add_reference_annotations"));
+ menuItem.setEnabled(false);
+ if (forSequences == null)
+ {
+ return;
+ }
+
+ /*
+ * Temporary store to hold distinct calcId / type pairs for the tooltip.
+ * Using TreeMap means calcIds are shown in alphabetical order.
+ */
+ Map<String, String> tipEntries = new TreeMap<String, String>();
+ StringBuilder tooltip = new StringBuilder(64);
+ tooltip.append(MessageManager.getString("label.add_annotations_for"));
+
+ /*
+ * For each sequence selected in the alignment, make a list of any
+ * annotations on the underlying dataset sequence which are not already on
+ * the sequence in the alignment.
+ *
+ * Build a map of { alignmentSequence, <List of annotations to add> }
+ */
+ final Map<SequenceI, List<AlignmentAnnotation>> candidates = new LinkedHashMap<SequenceI, List<AlignmentAnnotation>>();
+ for (SequenceI seq : forSequences)
+ {
+ SequenceI dataset = seq.getDatasetSequence();
+ if (dataset == null)
+ {
+ continue;
+ }
+ AlignmentAnnotation[] datasetAnnotations = dataset.getAnnotation();
+ if (datasetAnnotations == null)
+ {
+ continue;
+ }
+ final List<AlignmentAnnotation> result = new ArrayList<AlignmentAnnotation>();
+ for (AlignmentAnnotation dsann : datasetAnnotations)
+ {
+ /*
+ * If the sequence has no annotation that matches this one, then add
+ * this one to the results list.
+ */
+ if (seq.getAlignmentAnnotations(dsann.getCalcId(), dsann.label)
+ .isEmpty())
+ {
+ result.add(dsann);
+ tipEntries.put(dsann.getCalcId(), dsann.label);
+ }
+ }
+ /*
+ * Save any addable annotations for this sequence
+ */
+ if (!result.isEmpty())
+ {
+ candidates.put(seq, result);
+ }
+ }
+ if (!candidates.isEmpty())
+ {
+ /*
+ * Found annotations that could be added. Enable the menu item, and
+ * configure its tooltip and action.
+ */
+ menuItem.setEnabled(true);
+ for (String calcId : tipEntries.keySet())
+ {
+ tooltip.append("<br/>" + calcId + "/" + tipEntries.get(calcId));
+ }
+ String tooltipText = JvSwingUtils.wrapTooltip(true,
+ tooltip.toString());
+ menuItem.setToolTipText(tooltipText);
+
+ menuItem.addActionListener(new ActionListener()
+ {
+ @Override
+ public void actionPerformed(ActionEvent e)
+ {
+ addReferenceAnnotations_actionPerformed(candidates);
+ }
+ });
+ }
+ }
+
+ /**
+ * Add annotations to the sequences and to the alignment.
+ *
+ * @param candidates
+ * a map whose keys are sequences on the alignment, and values a list
+ * of annotations to add to each sequence
+ */
+ protected void addReferenceAnnotations_actionPerformed(
+ Map<SequenceI, List<AlignmentAnnotation>> candidates)
+ {
+ /*
+ * Add annotations at the top of the annotation, in the same order as their
+ * related sequences.
+ */
+ int insertPosition = 0;
+ for (SequenceI seq : candidates.keySet())
+ {
+ for (AlignmentAnnotation ann : candidates.get(seq))
+ {
+ AlignmentAnnotation copyAnn = new AlignmentAnnotation(ann);
+ int startRes = 0;
+ int endRes = ann.annotations.length;
+ final SequenceGroup selectionGroup = this.ap.av.getSelectionGroup();
+ if (selectionGroup != null)
+ {
+ startRes = selectionGroup.getStartRes();
+ endRes = selectionGroup.getEndRes();
+ }
+ copyAnn.restrict(startRes, endRes);
+
+ // add to the sequence (sets copyAnn.datasetSequence)
+ seq.addAlignmentAnnotation(copyAnn);
+ // adjust for gaps
+ copyAnn.adjustForAlignment();
+ // add to the alignment and set visible
+ this.ap.getAlignment().addAnnotation(copyAnn, insertPosition++);
+ copyAnn.visible = true;
+ }
+ }
+ refresh();
+ }
+
protected void sequenceSelectionDetails_actionPerformed()
{
createSequenceDetailsReport(ap.av.getSequenceSelection());
if (sg != null)
{
if (sequence == null)
+ {
sequence = sg.getSequenceAt(0);
+ }
EditNameDialog dialog = new EditNameDialog(
sequence.getSequenceAsString(sg.getStartRes(),