--- /dev/null
--- /dev/null
++package jalview.analysis;
++
++import jalview.datamodel.AlignmentAnnotation;
++import jalview.datamodel.SequenceI;
++import jalview.renderer.AnnotationRenderer;
++
++import java.util.ArrayList;
++import java.util.Arrays;
++import java.util.BitSet;
++import java.util.Collections;
++import java.util.HashMap;
++import java.util.List;
++import java.util.Map;
++
++public class AlignmentAnnotationUtils
++{
++
++ /**
++ * 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 list of all the annotation types that belong to the group</li>
++ * </ul>
++ *
++ * @param shownTypes
++ * a map, keyed by calcId (annotation source), whose entries are the
++ * lists of annotation types found for the calcId; each annotation
++ * type in turn may be a list (in the case of grouped annotations)
++ * @param hiddenTypes
++ * a map, similar to shownTypes, but for hidden annotation types
++ * @param annotations
++ * the annotations on the alignment to scan
++ * @param forSequences
++ * the sequences to restrict search to
++ */
++ public static void getShownHiddenTypes(
++ Map<String, List<List<String>>> shownTypes,
++ Map<String, List<List<String>>> hiddenTypes,
++ List<AlignmentAnnotation> annotations,
++ List<SequenceI> forSequences)
++ {
++ BitSet visibleGraphGroups = AlignmentAnnotationUtils
++ .getVisibleLineGraphGroups(annotations);
++
++ /*
++ * Build a lookup, by calcId (annotation source), of all annotation types in
++ * each graph group.
++ */
++ Map<String, Map<Integer, List<String>>> groupLabels = new HashMap<String, Map<Integer, List<String>>>();
++
++ // trackers for which calcId!label combinations we have dealt with
++ List<String> addedToShown = new ArrayList<String>();
++ List<String> addedToHidden = new ArrayList<String>();
++
++ for (AlignmentAnnotation aa : annotations)
++ {
++ if (forSequences != null
++ && (aa.sequenceRef != null && forSequences
++ .contains(aa.sequenceRef)))
++ {
++ String calcId = aa.getCalcId();
++
++ /*
++ * Build a 'composite label' for types in line graph groups.
++ */
++ final List<String> labelAsList = new ArrayList<String>();
++ final String displayLabel = aa.label;
++ labelAsList.add(displayLabel);
++ if (aa.graph == AlignmentAnnotation.LINE_GRAPH
++ && aa.graphGroup > -1)
++ {
++ if (!groupLabels.containsKey(calcId))
++ {
++ groupLabels.put(calcId, new HashMap<Integer, List<String>>());
++ }
++ Map<Integer, List<String>> groupLabelsForCalcId = groupLabels
++ .get(calcId);
++ if (groupLabelsForCalcId.containsKey(aa.graphGroup))
++ {
++ if (!groupLabelsForCalcId.get(aa.graphGroup).contains(
++ displayLabel))
++ {
++ groupLabelsForCalcId.get(aa.graphGroup).add(displayLabel);
++ }
++ }
++ else
++ {
++ groupLabelsForCalcId.put(aa.graphGroup, labelAsList);
++ }
++ }
++ else
++ /*
++ * 'Simple case' - not a grouped annotation type - list of one label
++ * only
++ */
++ {
++ String rememberAs = calcId + "!" + displayLabel;
++ if (aa.visible && !addedToShown.contains(rememberAs))
++ {
++ if (!shownTypes.containsKey(calcId))
++ {
++ shownTypes.put(calcId, new ArrayList<List<String>>());
++ }
++ shownTypes.get(calcId).add(labelAsList);
++ addedToShown.add(rememberAs);
++ }
++ else
++ {
++ if (!aa.visible && !addedToHidden.contains(rememberAs))
++ {
++ if (!hiddenTypes.containsKey(calcId))
++ {
++ hiddenTypes.put(calcId, new ArrayList<List<String>>());
++ }
++ hiddenTypes.get(calcId).add(labelAsList);
++ addedToHidden.add(rememberAs);
++ }
++ }
++ }
++ }
++ }
++ /*
++ * finally add the 'composite group labels' to the appropriate lists,
++ * depending on whether the group is identified as visible or hidden
++ */
++ for (String calcId : groupLabels.keySet())
++ {
++ for (int group : groupLabels.get(calcId).keySet())
++ {
++ final List<String> groupLabel = groupLabels.get(calcId).get(group);
++ if (visibleGraphGroups.get(group))
++ {
++ if (!shownTypes.containsKey(calcId))
++ {
++ shownTypes.put(calcId, new ArrayList<List<String>>());
++ }
++ shownTypes.get(calcId).add(groupLabel);
++ }
++ else
++ {
++ if (!hiddenTypes.containsKey(calcId))
++ {
++ hiddenTypes.put(calcId, new ArrayList<List<String>>());
++ }
++ hiddenTypes.get(calcId).add(groupLabel);
++ }
++ }
++ }
++ }
++
++ /**
++ * Returns a BitSet (possibly empty) of those graphGroups for line graph
++ * annotations, which have at least one member annotation row marked visible.
++ * <p/>
++ * Only one row in each visible 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 apparently marked not
++ * visible are in fact shown.
++ *
++ * @see AnnotationRenderer#drawComponent
++ * @param annotations
++ * @return
++ */
++ public static BitSet getVisibleLineGraphGroups(
++ List<AlignmentAnnotation> annotations)
++ {
++ BitSet result = new BitSet();
++ for (AlignmentAnnotation ann : annotations)
++ {
++ if (ann.graph == AlignmentAnnotation.LINE_GRAPH && ann.visible)
++ {
++ int gg = ann.graphGroup;
++ if (gg > -1)
++ {
++ result.set(gg);
++ }
++ }
++ }
++ return result;
++ }
++
++ /**
++ * Converts an array of AlignmentAnnotation into a List of
++ * AlignmentAnnotation. A null array is converted to an empty list.
++ *
++ * @param anns
++ * @return
++ */
++ public static List<AlignmentAnnotation> asList(
++ AlignmentAnnotation[] anns)
++ {
++ // TODO use AlignmentAnnotationI instead when it exists
++ return (anns == null ? Collections.<AlignmentAnnotation> emptyList()
++ : Arrays.asList(anns));
++ }
++}
public char[] getSequence(int start, int end)
{
if (start < 0)
++ {
start = 0;
++ }
// JBPNote - left to user to pad the result here (TODO:Decide on this
// policy)
if (start >= sequence.length)
{
this.annotation.removeElement(annotation);
if (this.annotation.size() == 0)
++ {
this.annotation = null;
++ }
}
}
for (int i = 0; i < annotations.length; i++)
{
if (annotations[i] != null)
++ {
addAlignmentAnnotation(annotations[i]);
++ }
}
}
}
return anns;
}
-- /**
-- * Returns a list of any annotations on the sequence that match the given
-- * calcId (source) and label (type). Null values do not match.
-- *
-- * @param calcId
-- * @param label
-- * @return
-- */
-- @Override
-- public List<AlignmentAnnotation> getAlignmentAnnotations(String calcId,
-- String label)
-- {
-- List<AlignmentAnnotation> result = new ArrayList<AlignmentAnnotation>();
-- if (annotation != null)
-- {
-- for (AlignmentAnnotation ann : annotation)
-- {
-- if (ann.calcId != null && ann.calcId.equals(calcId)
-- && ann.label != null && ann.label.equals(label))
-- {
-- result.add(ann);
-- }
-- }
-- }
-- return result;
-- }
--
public boolean updatePDBIds()
{
if (datasetSequence != null)
return rna;
}
++ /**
++ * Returns a (possibly empty) list of any annotations that match on given
++ * calcId (source) and label (type). Null values do not match.
++ *
++ * @param calcId
++ * @param label
++ */
++ @Override
++ public List<AlignmentAnnotation> getAlignmentAnnotations(String calcId,
++ String label)
++ {
++ List<AlignmentAnnotation> result = new ArrayList<AlignmentAnnotation>();
++ if (this.annotation != null) {
++ for (AlignmentAnnotation ann : annotation) {
++ if (ann.calcId != null && ann.calcId.equals(calcId)
++ && ann.label != null && ann.label.equals(label))
++ {
++ result.add(ann);
++ }
++ }
++ }
++ return result;
++ }
++
}
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 addDatasequenceAnnotations = new JMenuItem();
++ JMenuItem addReferenceAnnotations = new JMenuItem();
JMenuItem sequenceFeature = 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 ActionListener()
++ menuItem.addActionListener(new ActionListener()
{
@Override
-- public void actionPerformed(ActionEvent e) {
-- // TODO re JAL-860: optionally open dialog or provide a menu entry
-- // allowing user to open just one structure per sequence
-- // new AppJmol(pdb, ap.av.collateForPDB(new PDBEntry[]
-- // { pdb })[0], null, ap);
-- new StructureViewer(ap.getStructureSelectionManager())
-- .viewStructures(pdb,
-- ap.av.collateForPDB(new PDBEntry[]
-- { pdb })[0], null, ap);
++ public void actionPerformed(ActionEvent e)
++ {
++ // TODO re JAL-860: optionally open dialog or provide a menu entry
++ // allowing user to open just one structure per sequence
++ // new AppJmol(pdb, ap.av.collateForPDB(new PDBEntry[]
++ // { pdb })[0], null, ap);
++ new StructureViewer(ap.getStructureSelectionManager())
++ .viewStructures(pdb,
++ ap.av.collateForPDB(new PDBEntry[]
++ { pdb })[0], null, ap);
}
});
viewStructureMenu.add(menuItem);
* 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);
/*
* Find shown/hidden annotations types, distinguished by source (calcId),
*/
Map<String, List<List<String>>> shownTypes = new HashMap<String, List<List<String>>>();
Map<String, List<List<String>>> hiddenTypes = new HashMap<String, List<List<String>>>();
-- PopupMenu.getAnnotationTypesForShowHide(shownTypes, hiddenTypes,
-- visibleGraphGroups, annotations, selectionGroup);
++ AlignmentAnnotationUtils.getShownHiddenTypes(shownTypes,
++ hiddenTypes,
++ AlignmentAnnotationUtils.asList(annotations),
++ forSequences);
for (String calcId : hiddenTypes.keySet())
{
for (List<String> type : hiddenTypes.get(calcId))
{
-- addAnnotationTypeToShowHide(showAnnotationsMenu, calcId, type,
-- false, true);
++ addAnnotationTypeToShowHide(showAnnotationsMenu, forSequences,
++ calcId, type, false, true);
}
}
++ // grey out 'show annotations' if none are hidden
++ showAnnotationsMenu.setEnabled(!hiddenTypes.isEmpty());
for (String calcId : shownTypes.keySet())
{
for (List<String> type : shownTypes.get(calcId))
{
-- addAnnotationTypeToShowHide(hideAnnotationsMenu, calcId, type,
-- false, false);
++ addAnnotationTypeToShowHide(hideAnnotationsMenu, forSequences,
++ calcId, type, false, false);
}
}
++ // grey out 'hide annotations' if none are shown
++ hideAnnotationsMenu.setEnabled(!shownTypes.isEmpty());
}
/**
-- * 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>
++ * Returns a list of sequences - either the current selection group (if there
++ * is one), else the specified single sequence.
*
-- * @param shownTypes
-- * a map, keyed by calcId (annotation source), whose entries are the
-- * lists of annotation types found for the calcId; each annotation
-- * type in turn may be a list (in the case of grouped annotations)
-- * @param hiddenTypes
-- * a map, similar to shownTypes, but for hidden annotation types
-- * @param visibleGraphGroups
-- * a lookup keyed by graphGroup identifier
-- * @param annotations
-- * the annotations on the alignment to scan
-- * @param sequenceGroup
-- * the sequence group to restrict search to
++ * @param seq
++ * @return
*/
-- public static void getAnnotationTypesForShowHide(
-- Map<String, List<List<String>>> shownTypes,
-- Map<String, List<List<String>>> hiddenTypes,
-- BitSet visibleGraphGroups, AlignmentAnnotation[] annotations,
-- SequenceGroup sequenceGroup)
++ protected List<SequenceI> getSequenceScope(SequenceI seq)
{
-- /*
-- * Build a lookup, by calcId (annotation source), of all annotation types in
-- * each graph group.
-- */
-- Map<String, Map<Integer, List<String>>> groupLabels = new HashMap<String, Map<Integer, List<String>>>();
--
-- // trackers for which calcId!label combinations we have dealt with
-- List<String> addedToShown = new ArrayList<String>();
-- List<String> addedToHidden = new ArrayList<String>();
--
-- for (AlignmentAnnotation aa : annotations)
-- {
--
-- if (sequenceGroup == null
-- || (aa.sequenceRef != null && sequenceGroup.getSequences()
-- .contains(aa.sequenceRef)))
-- {
-- String calcId = aa.getCalcId();
--
-- /*
-- * Build a 'composite label' for types in line graph groups.
-- */
-- final List<String> labelAsList = new ArrayList<String>();
-- final String displayLabel = aa.label;
-- labelAsList.add(displayLabel);
-- if (aa.graph == AlignmentAnnotation.LINE_GRAPH
-- && aa.graphGroup > -1)
-- {
-- if (!groupLabels.containsKey(calcId))
-- {
-- groupLabels.put(calcId, new HashMap<Integer, List<String>>());
-- }
-- Map<Integer, List<String>> groupLabelsForCalcId = groupLabels
-- .get(calcId);
-- if (groupLabelsForCalcId.containsKey(aa.graphGroup))
-- {
-- if (!groupLabelsForCalcId.get(aa.graphGroup).contains(
-- displayLabel))
-- {
-- groupLabelsForCalcId.get(aa.graphGroup).add(displayLabel);
-- }
-- }
-- else
-- {
-- groupLabelsForCalcId.put(aa.graphGroup, labelAsList);
-- }
-- }
-- else
-- /*
-- * 'Simple case' - not a grouped annotation type - list of one label
-- * only
-- */
-- {
-- String rememberAs = calcId + "!" + displayLabel;
-- if (aa.visible && !addedToShown.contains(rememberAs))
-- {
-- if (!shownTypes.containsKey(calcId))
-- {
-- shownTypes.put(calcId, new ArrayList<List<String>>());
-- }
-- shownTypes.get(calcId).add(labelAsList);
-- addedToShown.add(rememberAs);
-- }
-- else
-- {
-- if (!aa.visible && !addedToHidden.contains(rememberAs))
-- {
-- if (!hiddenTypes.containsKey(calcId))
-- {
-- hiddenTypes.put(calcId, new ArrayList<List<String>>());
-- }
-- hiddenTypes.get(calcId).add(labelAsList);
-- addedToHidden.add(rememberAs);
-- }
-- }
-- }
-- }
-- }
-- /*
-- * finally add the 'composite group labels' to the appropriate lists,
-- * depending on whether the group is identified as visible or hidden
-- */
-- for (String calcId : groupLabels.keySet())
++ List<SequenceI> forSequences = null;
++ final SequenceGroup selectionGroup = ap.av.getSelectionGroup();
++ if (selectionGroup != null && selectionGroup.getSize() > 0)
{
-- for (int group : groupLabels.get(calcId).keySet())
-- {
-- final List<String> groupLabel = groupLabels.get(calcId).get(group);
-- if (visibleGraphGroups.get(group))
-- {
-- if (!shownTypes.containsKey(calcId))
-- {
-- shownTypes.put(calcId, new ArrayList<List<String>>());
-- }
-- shownTypes.get(calcId).add(groupLabel);
-- }
-- else
-- {
-- if (!hiddenTypes.containsKey(calcId))
-- {
-- hiddenTypes.put(calcId, new ArrayList<List<String>>());
-- }
-- hiddenTypes.get(calcId).add(groupLabel);
-- }
-- }
++ forSequences = selectionGroup.getSequences();
}
-- }
--
-- /**
-- * 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.
-- *
-- * @see AnnotationRenderer#drawComponent
-- * @param annotations
-- * @return
-- */
-- public static BitSet getVisibleLineGraphGroups(
-- AlignmentAnnotation[] annotations)
-- {
-- // todo move to a utility class
-- BitSet result = new BitSet();
-- for (AlignmentAnnotation ann : annotations)
++ else
{
-- if (ann.graph == AlignmentAnnotation.LINE_GRAPH && ann.visible)
-- {
-- int gg = ann.graphGroup;
-- if (gg > -1)
-- {
-- result.set(gg);
-- }
-- }
++ forSequences = seq == null ? Collections.<SequenceI> emptyList()
++ : Arrays.asList(seq);
}
-- return result;
++ 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
* type, else hide
*/
protected void addAnnotationTypeToShowHide(JMenu showOrHideMenu,
-- String calcId, final List<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]
@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, boolean anyType, boolean doShow)
++ Collection<String> types, List<SequenceI> forSequences,
++ boolean anyType, boolean doShow)
{
for (AlignmentAnnotation aa : ap.getAlignment()
.getAlignmentAnnotation())
{
if (anyType || types.contains(aa.label))
{
if ((aa.sequenceRef != null)
-- && ap.av.getSelectionGroup().getSequences()
-- .contains(aa.sequenceRef))
++ && forSequences.contains(aa.sequenceRef))
{
aa.visible = doShow;
}
.getString("label.show_annotations"));
hideAnnotationsMenu.setText(MessageManager
.getString("label.hide_annotations"));
-- configureReferenceAnnotationsMenu();
sequenceFeature.setText(MessageManager
.getString("label.create_sequence_feature"));
sequenceFeature.addActionListener(new ActionListener()
add(groupMenu);
add(sequenceMenu);
this.add(structureMenu);
++ // annotations configuration panel suppressed for now
// groupMenu.add(chooseAnnotations);
-- groupMenu.add(showAnnotationsMenu);
-- groupMenu.add(hideAnnotationsMenu);
-- groupMenu.add(addDatasequenceAnnotations);
++
++ /*
++ * 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. If any are found,
-- * enable the option to add them to the alignment. The criteria for 'on the
-- * alignment' is finding an annotation that matches on
-- * sequenceRef.datasetSequence, calcId and label.
++ * 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()
++ protected void configureReferenceAnnotationsMenu(
++ JMenuItem menuItem, List<SequenceI> forSequences)
{
-- addDatasequenceAnnotations.setText(MessageManager
++ menuItem.setText(MessageManager
.getString("label.add_reference_annotations"));
-- addDatasequenceAnnotations.setEnabled(false);
++ menuItem.setEnabled(false);
++ if (forSequences == null)
++ {
++ return;
++ }
/*
-- * Temporary store so we can write distinct calcId / type pairs on the
-- * tooltip.
++ * 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 HashMap<String, String>();
++ Map<String, String> tipEntries = new TreeMap<String, String>();
StringBuilder tooltip = new StringBuilder(64);
tooltip.append(MessageManager.getString("label.add_annotations_for"));
-- // this menu option only applies for a Selection
-- if (this.ap.av.getSelectionGroup() == null)
-- {
-- return;
-- }
--
/*
* For each sequence selected in the alignment, make a list of any
* annotations on the underlying dataset sequence which are not already on
*
* Build a map of { alignmentSequence, <List of annotations to add> }
*/
-- final Map<SequenceI, List<AlignmentAnnotation>> candidates = new HashMap<SequenceI, List<AlignmentAnnotation>>();
-- for (SequenceI seq : this.ap.av.getSelectionGroup().getSequences())
++ final Map<SequenceI, List<AlignmentAnnotation>> candidates = new LinkedHashMap<SequenceI, List<AlignmentAnnotation>>();
++ for (SequenceI seq : forSequences)
{
SequenceI dataset = seq.getDatasetSequence();
if (dataset == null)
* Found annotations that could be added. Enable the menu item, and
* configure its tooltip and action.
*/
-- addDatasequenceAnnotations.setEnabled(true);
++ menuItem.setEnabled(true);
for (String calcId : tipEntries.keySet())
{
tooltip.append("<br/>" + calcId + "/" + tipEntries.get(calcId));
}
String tooltipText = JvSwingUtils.wrapTooltip(true,
tooltip.toString());
-- addDatasequenceAnnotations.setToolTipText(tooltipText);
++ menuItem.setToolTipText(tooltipText);
-- addDatasequenceAnnotations.addActionListener(new ActionListener()
++ menuItem.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
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))
}
copyAnn.restrict(startRes, endRes);
-- // add to the sequence (sets correct copyAnn.datasetSequence)
++ // 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);
++ this.ap.getAlignment().addAnnotation(copyAnn, insertPosition++);
copyAnn.visible = true;
}
}
if (sg != null)
{
if (sequence == null)
++ {
sequence = sg.getSequenceAt(0);
++ }
EditNameDialog dialog = new EditNameDialog(
sequence.getSequenceAsString(sg.getStartRes(),
--package jalview.gui;
++package jalview.analysis;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import jalview.datamodel.AlignmentAnnotation;
import jalview.datamodel.AlignmentI;
--import jalview.datamodel.SequenceGroup;
import jalview.datamodel.SequenceI;
import jalview.io.AppletFormatAdapter;
import java.io.IOException;
++import java.util.ArrayList;
import java.util.BitSet;
++import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
--/**
-- * Unit tests for PopupMenu
-- *
-- * @author gmcarstairs
-- *
-- */
--public class PopupMenuTest
++public class AlignmentAnnotationUtilsTest
{
// 4 sequences x 13 positions
-- final static String TEST_DATA = ">FER_CAPAA Ferredoxin\n"
-- + "TIETHKEAELVG-\n"
-- + ">FER_CAPAN Ferredoxin, chloroplast precursor\n"
-- + "TIETHKEAELVG-\n"
-- + ">FER1_SOLLC Ferredoxin-1, chloroplast precursor\n"
-- + "TIETHKEEELTA-\n" + ">Q93XJ9_SOLTU Ferredoxin I precursor\n"
-- + "TIETHKEEELTA-\n";
++ final static String EOL = "\n";
++
++ // @formatter:off
++ final static String TEST_DATA =
++ ">FER_CAPAA Ferredoxin" + EOL +
++ "TIETHKEAELVG-" + EOL +
++ ">FER_CAPAN Ferredoxin, chloroplast precursor" + EOL +
++ "TIETHKEAELVG-" + EOL +
++ ">FER1_SOLLC Ferredoxin-1, chloroplast precursor" + EOL +
++ "TIETHKEEELTA-" + EOL +
++ ">Q93XJ9_SOLTU Ferredoxin I precursor" + EOL +
++ "TIETHKEEELTA-" + EOL;
++ // @formatter:on
private static final int SEQ_ANN_COUNT = 10;
-- private static final int AUTO_ANNS = 3;
++ private AlignmentI alignment;
-- AlignmentI alignment;
++ /**
++ * Test method that converts a (possibly null) array to a list.
++ */
++ @Test
++ public void testAsList()
++ {
++ // null array
++ Collection<AlignmentAnnotation> c1 = AlignmentAnnotationUtils
++ .asList(null);
++ assertTrue(c1.isEmpty());
++
++ // empty array
++ AlignmentAnnotation[] anns = new AlignmentAnnotation[0];
++ c1 = AlignmentAnnotationUtils.asList(anns);
++ assertTrue(c1.isEmpty());
++
++ // non-empty array
++ anns = new AlignmentAnnotation[2];
++ anns[0] = new AlignmentAnnotation("label0", "desc0", 0.0f);
++ anns[1] = new AlignmentAnnotation("label1", "desc1", 1.0f);
++ c1 = AlignmentAnnotationUtils.asList(anns);
++ assertEquals(2, c1.size());
++ assertTrue(c1.contains(anns[0]));
++ assertTrue(c1.contains(anns[1]));
++ }
-- AlignmentPanel parentPanel;
++ /**
++ * This output is not part of the test but may help make sense of it...
++ *
++ * @param shownTypes
++ * @param hiddenTypes
++ */
++ protected void consoleDebug(Map<String, List<List<String>>> shownTypes,
++ Map<String, List<List<String>>> hiddenTypes)
++ {
++ for (String calcId : shownTypes.keySet())
++ {
++ System.out.println("Visible annotation types for calcId=" + calcId);
++ for (List<String> type : shownTypes.get(calcId))
++ {
++ System.out.println(" " + type);
++ }
++ }
++ for (String calcId : hiddenTypes.keySet())
++ {
++ System.out.println("Hidden annotation types for calcId=" + calcId);
++ for (List<String> type : hiddenTypes.get(calcId))
++ {
++ System.out.println(" " + type);
++ }
++ }
++ }
++
++ /**
++ * Add a sequence group to the alignment with the specified sequences (base 0)
++ * in it
++ *
++ * @param i
++ * @param more
++ */
++ private List<SequenceI> selectSequences(int... selected)
++ {
++ List<SequenceI> result = new ArrayList<SequenceI>();
++ SequenceI[] seqs = alignment.getSequencesArray();
++ for (int i : selected)
++ {
++ result.add(seqs[i]);
++ }
++ return result;
++ }
@Before
public void setUp() throws IOException
{
-- AlignmentI al = new jalview.io.FormatAdapter().readFile(TEST_DATA,
++ alignment = new jalview.io.FormatAdapter().readFile(TEST_DATA,
AppletFormatAdapter.PASTE, "FASTA");
-- AlignFrame af = new AlignFrame(al, 700, 500);
-- parentPanel = new AlignmentPanel(af, af.getViewport());
-- alignment = parentPanel.getAlignment();
--
++
AlignmentAnnotation[] anns = new AlignmentAnnotation[SEQ_ANN_COUNT];
for (int i = 0; i < anns.length; i++)
{
}
/**
* Test a mixture of show/hidden annotations in/outside selection group.
*/
@Test
-- public void testGetAnnotationTypesForShowHide_forSelectionGroup()
++ public void testGetShownHiddenTypes_forSelectionGroup()
{
Map<String, List<List<String>>> shownTypes = new HashMap<String, List<List<String>>>();
Map<String, List<List<String>>> hiddenTypes = new HashMap<String, List<List<String>>>();
AlignmentAnnotation[] anns = alignment.getAlignmentAnnotation();
-- BitSet visibleGraphGroups = new BitSet();
-- selectSequences(0, 3);
-- SequenceI[] seqs = parentPanel.getAlignment().getSequencesArray();
--
++ SequenceI[] seqs = alignment.getSequencesArray();
++
/*
-- * Configure annotation properties for test (offsetting for auto-calculated
-- * rows).
++ * Configure annotation properties for test
*/
// not in selection group (should be ignored):
// hidden annotation Label4 not in selection group
-- anns[AUTO_ANNS + 4].sequenceRef = seqs[2];
-- anns[AUTO_ANNS + 4].visible = false;
-- anns[AUTO_ANNS + 7].sequenceRef = seqs[1];
-- anns[AUTO_ANNS + 7].visible = true;
--
++ anns[4].sequenceRef = seqs[2];
++ anns[4].visible = false;
++ anns[7].sequenceRef = seqs[1];
++ anns[7].visible = true;
++
/*
* in selection group, hidden:
*/
-- anns[AUTO_ANNS + 2].sequenceRef = seqs[3]; // CalcId2/Label2
-- anns[AUTO_ANNS + 2].visible = false;
-- anns[AUTO_ANNS + 3].sequenceRef = seqs[3]; // CalcId3/Label2
-- anns[AUTO_ANNS + 3].visible = false;
-- anns[AUTO_ANNS + 3].label = "Label2";
-- anns[AUTO_ANNS + 4].sequenceRef = seqs[3]; // CalcId2/Label3
-- anns[AUTO_ANNS + 4].visible = false;
-- anns[AUTO_ANNS + 4].label = "Label3";
-- anns[AUTO_ANNS + 4].setCalcId("CalcId2");
-- anns[AUTO_ANNS + 8].sequenceRef = seqs[0]; // CalcId9/Label9
-- anns[AUTO_ANNS + 8].visible = false;
-- anns[AUTO_ANNS + 8].label = "Label9";
-- anns[AUTO_ANNS + 8].setCalcId("CalcId9");
++ anns[2].sequenceRef = seqs[3]; // CalcId2/Label2
++ anns[2].visible = false;
++ anns[3].sequenceRef = seqs[3]; // CalcId3/Label2
++ anns[3].visible = false;
++ anns[3].label = "Label2";
++ anns[4].sequenceRef = seqs[3]; // CalcId2/Label3
++ anns[4].visible = false;
++ anns[4].label = "Label3";
++ anns[4].setCalcId("CalcId2");
++ anns[8].sequenceRef = seqs[0]; // CalcId9/Label9
++ anns[8].visible = false;
++ anns[8].label = "Label9";
++ anns[8].setCalcId("CalcId9");
/*
* in selection group, visible
*/
-- anns[AUTO_ANNS + 6].sequenceRef = seqs[0]; // CalcId6/Label6
-- anns[AUTO_ANNS + 6].visible = true;
-- anns[AUTO_ANNS + 9].sequenceRef = seqs[3]; // CalcId9/Label9
-- anns[AUTO_ANNS + 9].visible = true;
--
-- PopupMenu.getAnnotationTypesForShowHide(shownTypes, hiddenTypes,
-- visibleGraphGroups, anns, parentPanel.av.getSelectionGroup());
--
++ anns[6].sequenceRef = seqs[0]; // CalcId6/Label6
++ anns[6].visible = true;
++ anns[9].sequenceRef = seqs[3]; // CalcId9/Label9
++ anns[9].visible = true;
++
++ List<SequenceI> selected = selectSequences(0, 3);
++ AlignmentAnnotationUtils.getShownHiddenTypes(shownTypes, hiddenTypes,
++ AlignmentAnnotationUtils.asList(anns),
++ selected);
++
// check results; note CalcId9/Label9 is both hidden and shown (for
// different sequences) so should be in both
// shown: CalcId6/Label6 and CalcId9/Label9
assertEquals(1, shownTypes.get("CalcId9").size());
assertEquals(1, shownTypes.get("CalcId9").get(0).size());
assertEquals("Label9", shownTypes.get("CalcId9").get(0).get(0));
--
++
// hidden: CalcId2/Label2, CalcId2/Label3, CalcId3/Label2, CalcId9/Label9
assertEquals(3, hiddenTypes.size());
assertEquals(2, hiddenTypes.get("CalcId2").size());
assertEquals(1, hiddenTypes.get("CalcId9").size());
assertEquals(1, hiddenTypes.get("CalcId9").get(0).size());
assertEquals("Label9", hiddenTypes.get("CalcId9").get(0).get(0));
--
++
consoleDebug(shownTypes, hiddenTypes);
}
/**
-- * This output is not part of the test but may help make sense of it...
-- *
-- * @param shownTypes
-- * @param hiddenTypes
-- */
-- protected void consoleDebug(Map<String, List<List<String>>> shownTypes,
-- Map<String, List<List<String>>> hiddenTypes)
-- {
-- for (String calcId : shownTypes.keySet())
-- {
-- System.out.println("Visible annotation types for calcId=" + calcId);
-- for (List<String> type : shownTypes.get(calcId))
-- {
-- System.out.println(" " + type);
-- }
-- }
-- for (String calcId : hiddenTypes.keySet())
-- {
-- System.out.println("Hidden annotation types for calcId=" + calcId);
-- for (List<String> type : hiddenTypes.get(calcId))
-- {
-- System.out.println(" " + type);
-- }
-- }
-- }
--
-- /**
* Test case where there are 'grouped' annotations, visible and hidden, within
* and without the selection group.
*/
@Test
-- public void testGetAnnotationTypesForShowHide_withGraphGroups()
++ public void testGetShownHiddenTypes_withGraphGroups()
{
final int GROUP_4 = 4;
final int GROUP_5 = 5;
final int GROUP_6 = 6;
--
++
Map<String, List<List<String>>> shownTypes = new HashMap<String, List<List<String>>>();
Map<String, List<List<String>>> hiddenTypes = new HashMap<String, List<List<String>>>();
AlignmentAnnotation[] anns = alignment.getAlignmentAnnotation();
-- BitSet visibleGraphGroups = new BitSet();
-- visibleGraphGroups.set(GROUP_4);
-- visibleGraphGroups.set(GROUP_6);
-- selectSequences(0, 3);
-- SequenceI[] seqs = parentPanel.getAlignment().getSequencesArray();
--
++ SequenceI[] seqs = alignment.getSequencesArray();
++
/*
-- * Configure annotation properties for test (offsetting for auto-calculated
-- * rows).
++ * Configure annotation properties for test
*/
// annotations for selection group and graph group
// hidden annotations Label2, Label3, in (hidden) group 5
-- anns[AUTO_ANNS + 2].sequenceRef = seqs[3];
-- anns[AUTO_ANNS + 2].visible = false;
-- anns[AUTO_ANNS + 2].graph = AlignmentAnnotation.LINE_GRAPH;
-- anns[AUTO_ANNS + 2].graphGroup = GROUP_5; // not a visible group
-- anns[AUTO_ANNS + 3].sequenceRef = seqs[0];
-- anns[AUTO_ANNS + 3].visible = false;
-- anns[AUTO_ANNS + 3].graph = AlignmentAnnotation.LINE_GRAPH;
-- anns[AUTO_ANNS + 3].graphGroup = GROUP_5;
++ anns[2].sequenceRef = seqs[3];
++ anns[2].visible = false;
++ anns[2].graph = AlignmentAnnotation.LINE_GRAPH;
++ anns[2].graphGroup = GROUP_5; // not a visible group
++ anns[3].sequenceRef = seqs[0];
++ anns[3].visible = false;
++ anns[3].graph = AlignmentAnnotation.LINE_GRAPH;
++ anns[3].graphGroup = GROUP_5;
// need to ensure annotations have the same calcId as well
-- anns[AUTO_ANNS + 3].setCalcId("CalcId2");
--
++ anns[3].setCalcId("CalcId2");
++
// annotations Label1 (hidden), Label5 (visible) in group 6 (visible)
-- anns[AUTO_ANNS + 1].sequenceRef = seqs[3];
++ anns[1].sequenceRef = seqs[3];
// being in a visible group should take precedence over this visibility
-- anns[AUTO_ANNS + 1].visible = false;
-- anns[AUTO_ANNS + 1].graph = AlignmentAnnotation.LINE_GRAPH;
-- anns[AUTO_ANNS + 1].graphGroup = GROUP_6;
-- anns[AUTO_ANNS + 5].sequenceRef = seqs[0];
-- anns[AUTO_ANNS + 5].visible = true; // visibleGraphGroups overrides this
-- anns[AUTO_ANNS + 5].graph = AlignmentAnnotation.LINE_GRAPH;
-- anns[AUTO_ANNS + 5].graphGroup = GROUP_6;
-- anns[AUTO_ANNS + 5].setCalcId("CalcId1");
--
++ anns[1].visible = false;
++ anns[1].graph = AlignmentAnnotation.LINE_GRAPH;
++ anns[1].graphGroup = GROUP_6;
++ anns[5].sequenceRef = seqs[0];
++ anns[5].visible = true;
++ anns[5].graph = AlignmentAnnotation.LINE_GRAPH;
++ anns[5].graphGroup = GROUP_6;
++ anns[5].setCalcId("CalcId1");
++
// annotations outwith selection group - should be ignored
// hidden grouped annotations
-- anns[AUTO_ANNS + 6].sequenceRef = seqs[2];
-- anns[AUTO_ANNS + 6].visible = false;
-- anns[AUTO_ANNS + 6].graph = AlignmentAnnotation.LINE_GRAPH;
-- anns[AUTO_ANNS + 6].graphGroup = GROUP_4;
-- anns[AUTO_ANNS + 8].sequenceRef = seqs[1];
-- anns[AUTO_ANNS + 8].visible = false;
-- anns[AUTO_ANNS + 8].graph = AlignmentAnnotation.LINE_GRAPH;
-- anns[AUTO_ANNS + 8].graphGroup = GROUP_4;
++ anns[6].sequenceRef = seqs[2];
++ anns[6].visible = false;
++ anns[6].graph = AlignmentAnnotation.LINE_GRAPH;
++ anns[6].graphGroup = GROUP_4;
++ anns[8].sequenceRef = seqs[1];
++ anns[8].visible = false;
++ anns[8].graph = AlignmentAnnotation.LINE_GRAPH;
++ anns[8].graphGroup = GROUP_4;
// visible grouped annotations Label7, Label9
-- anns[AUTO_ANNS + 7].sequenceRef = seqs[2];
-- anns[AUTO_ANNS + 7].visible = true;
-- anns[AUTO_ANNS + 7].graph = AlignmentAnnotation.LINE_GRAPH;
-- anns[AUTO_ANNS + 7].graphGroup = GROUP_4;
-- anns[AUTO_ANNS + 9].sequenceRef = seqs[1];
-- anns[AUTO_ANNS + 9].visible = true;
-- anns[AUTO_ANNS + 9].graph = AlignmentAnnotation.LINE_GRAPH;
-- anns[AUTO_ANNS + 9].graphGroup = GROUP_4;
--
-- PopupMenu.getAnnotationTypesForShowHide(shownTypes, hiddenTypes,
-- visibleGraphGroups, anns, parentPanel.av.getSelectionGroup());
--
++ anns[7].sequenceRef = seqs[2];
++ anns[7].visible = true;
++ anns[7].graph = AlignmentAnnotation.LINE_GRAPH;
++ anns[7].graphGroup = GROUP_4;
++ anns[9].sequenceRef = seqs[1];
++ anns[9].visible = true;
++ anns[9].graph = AlignmentAnnotation.LINE_GRAPH;
++ anns[9].graphGroup = GROUP_4;
++
++ List<SequenceI> selected = selectSequences(0, 3);
++ AlignmentAnnotationUtils.getShownHiddenTypes(shownTypes, hiddenTypes,
++ AlignmentAnnotationUtils.asList(anns),
++ selected);
++
consoleDebug(shownTypes, hiddenTypes);
--
++
// CalcId1 / Label1, Label5 (only) should be 'shown', as a compound type
assertEquals(1, shownTypes.get("CalcId1").size());
assertEquals(2, shownTypes.get("CalcId1").get(0).size());
assertEquals("Label1", shownTypes.get("CalcId1").get(0).get(0));
assertEquals("Label5", shownTypes.get("CalcId1").get(0).get(1));
--
++
// CalcId2 / Label2, Label3 (only) should be 'hidden'
assertEquals(1, hiddenTypes.get("CalcId2").size());
assertEquals(2, hiddenTypes.get("CalcId2").get(0).size());
}
/**
-- * Add a sequence group to the alignment with the specified sequences (base 0)
-- * in it
-- *
-- * @param i
-- * @param more
++ * Test method that determines visible graph groups.
*/
-- private void selectSequences(int... selected)
++ @Test
++ public void testGetVisibleGraphGroups()
{
-- SequenceI[] seqs = parentPanel.getAlignment().getSequencesArray();
-- SequenceGroup sg = new SequenceGroup();
-- for (int i : selected)
-- {
-- sg.addSequence(seqs[i], false);
-- }
-- parentPanel.av.setSelectionGroup(sg);
++ AlignmentAnnotation[] anns = alignment.getAlignmentAnnotation();
++ /*
++ * a bar graph group is not included
++ */
++ anns[0].graph = AlignmentAnnotation.BAR_GRAPH;
++ anns[0].graphGroup = 1;
++ anns[0].visible = true;
++
++ /*
++ * a line graph group is included as long as one of its members is visible
++ */
++ anns[1].graph = AlignmentAnnotation.LINE_GRAPH;
++ anns[1].graphGroup = 5;
++ anns[1].visible = false;
++ anns[2].graph = AlignmentAnnotation.LINE_GRAPH;
++ anns[2].graphGroup = 5;
++ anns[2].visible = true;
++
++ /*
++ * a line graph group with no visible rows is not included
++ */
++ anns[3].graph = AlignmentAnnotation.LINE_GRAPH;
++ anns[3].graphGroup = 3;
++ anns[3].visible = false;
++
++ // a visible line graph with no graph group is not included
++ anns[4].graph = AlignmentAnnotation.LINE_GRAPH;
++ anns[4].graphGroup = -1;
++ anns[4].visible = true;
++
++ BitSet result = AlignmentAnnotationUtils
++ .getVisibleLineGraphGroups(AlignmentAnnotationUtils
++ .asList(anns));
++ assertTrue(result.get(5));
++ assertFalse(result.get(0));
++ assertFalse(result.get(1));
++ assertFalse(result.get(2));
++ assertFalse(result.get(3));
++ }
++
++ /**
++ * Test for case where no sequence is selected. Shouldn't normally arise but
++ * check it handles it gracefully.
++ */
++ @Test
++ public void testGetShownHiddenTypes_noSequenceSelected()
++ {
++ Map<String, List<List<String>>> shownTypes = new HashMap<String, List<List<String>>>();
++ Map<String, List<List<String>>> hiddenTypes = new HashMap<String, List<List<String>>>();
++ AlignmentAnnotation[] anns = alignment.getAlignmentAnnotation();
++ // selected sequences null
++ AlignmentAnnotationUtils.getShownHiddenTypes(shownTypes, hiddenTypes,
++ AlignmentAnnotationUtils.asList(anns), null);
++ assertTrue(shownTypes.isEmpty());
++ assertTrue(hiddenTypes.isEmpty());
++
++ List<SequenceI> sequences = new ArrayList<SequenceI>();
++ // selected sequences empty list
++ AlignmentAnnotationUtils.getShownHiddenTypes(shownTypes, hiddenTypes,
++ AlignmentAnnotationUtils.asList(anns), sequences);
++ assertTrue(shownTypes.isEmpty());
++ assertTrue(hiddenTypes.isEmpty());
}
}