From: gmungoc Date: Thu, 23 Oct 2014 14:37:42 +0000 (+0100) Subject: JAL-1264 further refactoring and tests X-Git-Tag: Jalview_2_9~165 X-Git-Url: http://source.jalview.org/gitweb/?a=commitdiff_plain;h=195c41c18f6a059b334d063312cd15de6a937ea5;p=jalview.git JAL-1264 further refactoring and tests --- 195c41c18f6a059b334d063312cd15de6a937ea5 diff --cc src/jalview/analysis/AlignmentAnnotationUtils.java index 0000000,0000000..b5f77fd new file mode 100644 --- /dev/null +++ b/src/jalview/analysis/AlignmentAnnotationUtils.java @@@ -1,0 -1,0 +1,206 @@@ ++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. ++ *

++ * 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. ++ *

++ * For rows that belong to a line graph group, so are always rendered ++ * together: ++ *

++ * ++ * @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>> shownTypes, ++ Map>> hiddenTypes, ++ List annotations, ++ List forSequences) ++ { ++ BitSet visibleGraphGroups = AlignmentAnnotationUtils ++ .getVisibleLineGraphGroups(annotations); ++ ++ /* ++ * Build a lookup, by calcId (annotation source), of all annotation types in ++ * each graph group. ++ */ ++ Map>> groupLabels = new HashMap>>(); ++ ++ // trackers for which calcId!label combinations we have dealt with ++ List addedToShown = new ArrayList(); ++ List addedToHidden = new ArrayList(); ++ ++ 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 labelAsList = new ArrayList(); ++ 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>()); ++ } ++ Map> 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>()); ++ } ++ shownTypes.get(calcId).add(labelAsList); ++ addedToShown.add(rememberAs); ++ } ++ else ++ { ++ if (!aa.visible && !addedToHidden.contains(rememberAs)) ++ { ++ if (!hiddenTypes.containsKey(calcId)) ++ { ++ hiddenTypes.put(calcId, new ArrayList>()); ++ } ++ 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 groupLabel = groupLabels.get(calcId).get(group); ++ if (visibleGraphGroups.get(group)) ++ { ++ if (!shownTypes.containsKey(calcId)) ++ { ++ shownTypes.put(calcId, new ArrayList>()); ++ } ++ shownTypes.get(calcId).add(groupLabel); ++ } ++ else ++ { ++ if (!hiddenTypes.containsKey(calcId)) ++ { ++ hiddenTypes.put(calcId, new ArrayList>()); ++ } ++ 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. ++ *

++ * 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. ++ *

++ * 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 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 asList( ++ AlignmentAnnotation[] anns) ++ { ++ // TODO use AlignmentAnnotationI instead when it exists ++ return (anns == null ? Collections. emptyList() ++ : Arrays.asList(anns)); ++ } ++} diff --cc src/jalview/datamodel/Sequence.java index f628699,f628699..68bf8f2 --- a/src/jalview/datamodel/Sequence.java +++ b/src/jalview/datamodel/Sequence.java @@@ -493,7 -493,7 +493,9 @@@ public class Sequence implements Sequen 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) @@@ -928,7 -928,7 +930,9 @@@ { this.annotation.removeElement(annotation); if (this.annotation.size() == 0) ++ { this.annotation = null; ++ } } } @@@ -1040,7 -1040,7 +1044,9 @@@ for (int i = 0; i < annotations.length; i++) { if (annotations[i] != null) ++ { addAlignmentAnnotation(annotations[i]); ++ } } } } @@@ -1082,33 -1082,33 +1088,6 @@@ 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 getAlignmentAnnotations(String calcId, -- String label) -- { -- List result = new ArrayList(); -- 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) @@@ -1264,4 -1264,4 +1243,28 @@@ 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 getAlignmentAnnotations(String calcId, ++ String label) ++ { ++ List result = new ArrayList(); ++ 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; ++ } ++ } diff --cc src/jalview/gui/PopupMenu.java index 78c1592,78c1592..ad7726d --- a/src/jalview/gui/PopupMenu.java +++ b/src/jalview/gui/PopupMenu.java @@@ -21,6 -21,6 +21,7 @@@ package jalview.gui; import jalview.analysis.AAFrequency; ++import jalview.analysis.AlignmentAnnotationUtils; import jalview.analysis.Conservation; import jalview.commands.ChangeCaseCommand; import jalview.commands.EditCommand; @@@ -34,7 -34,7 +35,6 @@@ import jalview.datamodel.SequenceGroup 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; @@@ -60,12 -60,12 +60,14 @@@ 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.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; @@@ -192,7 -192,7 +194,7 @@@ public class PopupMenu extends JPopupMe JMenu hideAnnotationsMenu = new JMenu(); -- JMenuItem addDatasequenceAnnotations = new JMenuItem(); ++ JMenuItem addReferenceAnnotations = new JMenuItem(); JMenuItem sequenceFeature = new JMenuItem(); @@@ -279,9 -279,9 +281,18 @@@ } /* -- * 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 sequenceScope = getSequenceScope(seq); ++ if (!sequenceScope.isEmpty()) ++ { ++ buildAnnotationTypesMenus(sequenceScope); ++ configureReferenceAnnotationsMenu(addReferenceAnnotations, sequenceScope); ++ } try { @@@ -308,18 -308,18 +319,19 @@@ 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); @@@ -807,28 -807,28 +819,23 @@@ * composite type name, e.g. *

* IUPredWS (Long), IUPredWS (Short) ++ * ++ * @param forSequences */ -- protected void buildAnnotationTypesMenus() ++ protected void buildAnnotationTypesMenus(List forSequences) { showAnnotationsMenu.removeAll(); hideAnnotationsMenu.removeAll(); final List 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), @@@ -836,201 -836,201 +843,55 @@@ */ Map>> shownTypes = new HashMap>>(); Map>> hiddenTypes = new HashMap>>(); -- PopupMenu.getAnnotationTypesForShowHide(shownTypes, hiddenTypes, -- visibleGraphGroups, annotations, selectionGroup); ++ AlignmentAnnotationUtils.getShownHiddenTypes(shownTypes, ++ hiddenTypes, ++ AlignmentAnnotationUtils.asList(annotations), ++ forSequences); for (String calcId : hiddenTypes.keySet()) { for (List 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 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. -- *

-- * 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. -- *

-- * For rows that belong to a line graph group, so are always rendered -- * together: -- *

    -- *
  • Treat all rows in the group as visible, if at least one of them is
  • -- *
  • Build a comma-separated label with all the types that belong to the -- * group
  • -- *
++ * 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>> shownTypes, -- Map>> hiddenTypes, -- BitSet visibleGraphGroups, AlignmentAnnotation[] annotations, -- SequenceGroup sequenceGroup) ++ protected List getSequenceScope(SequenceI seq) { -- /* -- * Build a lookup, by calcId (annotation source), of all annotation types in -- * each graph group. -- */ -- Map>> groupLabels = new HashMap>>(); -- -- // trackers for which calcId!label combinations we have dealt with -- List addedToShown = new ArrayList(); -- List addedToHidden = new ArrayList(); -- -- 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 labelAsList = new ArrayList(); -- 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>()); -- } -- Map> 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>()); -- } -- shownTypes.get(calcId).add(labelAsList); -- addedToShown.add(rememberAs); -- } -- else -- { -- if (!aa.visible && !addedToHidden.contains(rememberAs)) -- { -- if (!hiddenTypes.containsKey(calcId)) -- { -- hiddenTypes.put(calcId, new ArrayList>()); -- } -- 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 forSequences = null; ++ final SequenceGroup selectionGroup = ap.av.getSelectionGroup(); ++ if (selectionGroup != null && selectionGroup.getSize() > 0) { -- for (int group : groupLabels.get(calcId).keySet()) -- { -- final List groupLabel = groupLabels.get(calcId).get(group); -- if (visibleGraphGroups.get(group)) -- { -- if (!shownTypes.containsKey(calcId)) -- { -- shownTypes.put(calcId, new ArrayList>()); -- } -- shownTypes.get(calcId).add(groupLabel); -- } -- else -- { -- if (!hiddenTypes.containsKey(calcId)) -- { -- hiddenTypes.put(calcId, new ArrayList>()); -- } -- 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. -- *

-- * 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. emptyList() ++ : Arrays.asList(seq); } -- return result; ++ return forSequences; } /** @@@ -1039,6 -1039,6 +900,8 @@@ * * @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 @@@ -1049,7 -1049,7 +912,8 @@@ * type, else hide */ protected void addAnnotationTypeToShowHide(JMenu showOrHideMenu, -- String calcId, final List types, final boolean allTypes, ++ final List forSequences, String calcId, ++ final List types, final boolean allTypes, final boolean actionIsShow) { String label = types.toString(); // [a, b, c] @@@ -1061,7 -1061,7 +925,8 @@@ @Override public void actionPerformed(ActionEvent e) { -- showHideAnnotation_actionPerformed(types, allTypes, actionIsShow); ++ showHideAnnotation_actionPerformed(types, forSequences, allTypes, ++ actionIsShow); } }); showOrHideMenu.add(item); @@@ -1069,26 -1069,26 +934,24 @@@ /** * 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 types, boolean anyType, boolean doShow) ++ Collection types, List 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; } @@@ -1586,7 -1586,7 +1449,6 @@@ .getString("label.show_annotations")); hideAnnotationsMenu.setText(MessageManager .getString("label.hide_annotations")); -- configureReferenceAnnotationsMenu(); sequenceFeature.setText(MessageManager .getString("label.create_sequence_feature")); sequenceFeature.addActionListener(new ActionListener() @@@ -1632,10 -1632,10 +1494,26 @@@ 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); @@@ -1871,31 -1871,31 +1749,35 @@@ /** * 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 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 tipEntries = new HashMap(); ++ Map tipEntries = new TreeMap(); 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 @@@ -1903,8 -1903,8 +1785,8 @@@ * * Build a map of { alignmentSequence, } */ -- final Map> candidates = new HashMap>(); -- for (SequenceI seq : this.ap.av.getSelectionGroup().getSequences()) ++ final Map> candidates = new LinkedHashMap>(); ++ for (SequenceI seq : forSequences) { SequenceI dataset = seq.getDatasetSequence(); if (dataset == null) @@@ -1944,16 -1944,16 +1826,16 @@@ * 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("
" + 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) @@@ -1974,6 -1974,6 +1856,11 @@@ protected void addReferenceAnnotations_actionPerformed( Map> 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)) @@@ -1989,12 -1989,12 +1876,12 @@@ } 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; } } @@@ -2790,7 -2790,7 +2677,9 @@@ if (sg != null) { if (sequence == null) ++ { sequence = sg.getSequenceAt(0); ++ } EditNameDialog dialog = new EditNameDialog( sequence.getSequenceAsString(sg.getStartRes(), diff --cc test/jalview/analysis/AlignmentAnnotationUtilsTest.java index 64215bb,64215bb..1da1939 --- a/test/jalview/analysis/AlignmentAnnotationUtilsTest.java +++ b/test/jalview/analysis/AlignmentAnnotationUtilsTest.java @@@ -1,16 -1,16 +1,17 @@@ --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; @@@ -18,40 -18,40 +19,104 @@@ 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 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>> shownTypes, ++ Map>> hiddenTypes) ++ { ++ for (String calcId : shownTypes.keySet()) ++ { ++ System.out.println("Visible annotation types for calcId=" + calcId); ++ for (List type : shownTypes.get(calcId)) ++ { ++ System.out.println(" " + type); ++ } ++ } ++ for (String calcId : hiddenTypes.keySet()) ++ { ++ System.out.println("Hidden annotation types for calcId=" + calcId); ++ for (List 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 selectSequences(int... selected) ++ { ++ List result = new ArrayList(); ++ 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++) { @@@ -63,100 -63,100 +128,55 @@@ } /** * Test a mixture of show/hidden annotations in/outside selection group. */ @Test -- public void testGetAnnotationTypesForShowHide_forSelectionGroup() ++ public void testGetShownHiddenTypes_forSelectionGroup() { Map>> shownTypes = new HashMap>>(); Map>> hiddenTypes = new HashMap>>(); 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 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 @@@ -167,7 -167,7 +187,7 @@@ 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()); @@@ -181,117 -181,117 +201,87 @@@ 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>> shownTypes, -- Map>> hiddenTypes) -- { -- for (String calcId : shownTypes.keySet()) -- { -- System.out.println("Visible annotation types for calcId=" + calcId); -- for (List type : shownTypes.get(calcId)) -- { -- System.out.println(" " + type); -- } -- } -- for (String calcId : hiddenTypes.keySet()) -- { -- System.out.println("Hidden annotation types for calcId=" + calcId); -- for (List 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>> shownTypes = new HashMap>>(); Map>> hiddenTypes = new HashMap>>(); 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 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()); @@@ -300,20 -300,20 +290,72 @@@ } /** -- * 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>> shownTypes = new HashMap>>(); ++ Map>> hiddenTypes = new HashMap>>(); ++ AlignmentAnnotation[] anns = alignment.getAlignmentAnnotation(); ++ // selected sequences null ++ AlignmentAnnotationUtils.getShownHiddenTypes(shownTypes, hiddenTypes, ++ AlignmentAnnotationUtils.asList(anns), null); ++ assertTrue(shownTypes.isEmpty()); ++ assertTrue(hiddenTypes.isEmpty()); ++ ++ List sequences = new ArrayList(); ++ // selected sequences empty list ++ AlignmentAnnotationUtils.getShownHiddenTypes(shownTypes, hiddenTypes, ++ AlignmentAnnotationUtils.asList(anns), sequences); ++ assertTrue(shownTypes.isEmpty()); ++ assertTrue(hiddenTypes.isEmpty()); } }