JAL-1152 menu tweaks plus new option to put Autocalc at top or below.
[jalview.git] / src / jalview / analysis / AlignmentAnnotationUtils.java
1 package jalview.analysis;
2
3 import jalview.datamodel.AlignmentAnnotation;
4 import jalview.datamodel.SequenceI;
5 import jalview.renderer.AnnotationRenderer;
6
7 import java.util.ArrayList;
8 import java.util.Arrays;
9 import java.util.BitSet;
10 import java.util.Collections;
11 import java.util.HashMap;
12 import java.util.List;
13 import java.util.Map;
14
15 public class AlignmentAnnotationUtils
16 {
17
18   /**
19    * Helper method to populate lists of annotation types for the Show/Hide
20    * Annotations menus. If sequenceGroup is not null, this is restricted to
21    * annotations which are associated with sequences in the selection group.
22    * <p/>
23    * If an annotation row is currently visible, its type (label) is added (once
24    * only per type), to the shownTypes list. If it is currently hidden, it is
25    * added to the hiddenTypesList.
26    * <p/>
27    * For rows that belong to a line graph group, so are always rendered
28    * together:
29    * <ul>
30    * <li>Treat all rows in the group as visible, if at least one of them is</li>
31    * <li>Build a list of all the annotation types that belong to the group</li>
32    * </ul>
33    * 
34    * @param shownTypes
35    *          a map, keyed by calcId (annotation source), whose entries are the
36    *          lists of annotation types found for the calcId; each annotation
37    *          type in turn may be a list (in the case of grouped annotations)
38    * @param hiddenTypes
39    *          a map, similar to shownTypes, but for hidden annotation types
40    * @param annotations
41    *          the annotations on the alignment to scan
42    * @param forSequences
43    *          the sequences to restrict search to
44    */
45   public static void getShownHiddenTypes(
46           Map<String, List<List<String>>> shownTypes,
47           Map<String, List<List<String>>> hiddenTypes,
48           List<AlignmentAnnotation> annotations,
49           List<SequenceI> forSequences)
50   {
51     BitSet visibleGraphGroups = AlignmentAnnotationUtils
52             .getVisibleLineGraphGroups(annotations);
53
54     /*
55      * Build a lookup, by calcId (annotation source), of all annotation types in
56      * each graph group.
57      */
58     Map<String, Map<Integer, List<String>>> groupLabels = new HashMap<String, Map<Integer, List<String>>>();
59
60     // trackers for which calcId!label combinations we have dealt with
61     List<String> addedToShown = new ArrayList<String>();
62     List<String> addedToHidden = new ArrayList<String>();
63
64     for (AlignmentAnnotation aa : annotations)
65     {
66       if (forSequences != null
67               && (aa.sequenceRef != null && forSequences
68                       .contains(aa.sequenceRef)))
69       {
70         String calcId = aa.getCalcId();
71
72         /*
73          * Build a 'composite label' for types in line graph groups.
74          */
75         final List<String> labelAsList = new ArrayList<String>();
76         final String displayLabel = aa.label;
77         labelAsList.add(displayLabel);
78         if (aa.graph == AlignmentAnnotation.LINE_GRAPH
79                 && aa.graphGroup > -1)
80         {
81           if (!groupLabels.containsKey(calcId))
82           {
83             groupLabels.put(calcId, new HashMap<Integer, List<String>>());
84           }
85           Map<Integer, List<String>> groupLabelsForCalcId = groupLabels
86                   .get(calcId);
87           if (groupLabelsForCalcId.containsKey(aa.graphGroup))
88           {
89             if (!groupLabelsForCalcId.get(aa.graphGroup).contains(
90                     displayLabel))
91             {
92               groupLabelsForCalcId.get(aa.graphGroup).add(displayLabel);
93             }
94           }
95           else
96           {
97             groupLabelsForCalcId.put(aa.graphGroup, labelAsList);
98           }
99         }
100         else
101         /*
102          * 'Simple case' - not a grouped annotation type - list of one label
103          * only
104          */
105         {
106           String rememberAs = calcId + "!" + displayLabel;
107           if (aa.visible && !addedToShown.contains(rememberAs))
108           {
109             if (!shownTypes.containsKey(calcId))
110             {
111               shownTypes.put(calcId, new ArrayList<List<String>>());
112             }
113             shownTypes.get(calcId).add(labelAsList);
114             addedToShown.add(rememberAs);
115           }
116           else
117           {
118             if (!aa.visible && !addedToHidden.contains(rememberAs))
119             {
120               if (!hiddenTypes.containsKey(calcId))
121               {
122                 hiddenTypes.put(calcId, new ArrayList<List<String>>());
123               }
124               hiddenTypes.get(calcId).add(labelAsList);
125               addedToHidden.add(rememberAs);
126             }
127           }
128         }
129       }
130     }
131     /*
132      * Finally add the 'composite group labels' to the appropriate lists,
133      * depending on whether the group is identified as visible or hidden. Don't
134      * add the same label more than once (there may be many graph groups that
135      * generate it).
136      */
137     for (String calcId : groupLabels.keySet())
138     {
139       for (int group : groupLabels.get(calcId).keySet())
140       {
141         final List<String> groupLabel = groupLabels.get(calcId).get(group);
142         // don't want to duplicate 'same types in different order'
143         Collections.sort(groupLabel);
144         if (visibleGraphGroups.get(group))
145         {
146           if (!shownTypes.containsKey(calcId))
147           {
148             shownTypes.put(calcId, new ArrayList<List<String>>());
149           }
150           if (!shownTypes.get(calcId).contains(groupLabel))
151           {
152             shownTypes.get(calcId).add(groupLabel);
153           }
154         }
155         else
156         {
157           if (!hiddenTypes.containsKey(calcId))
158           {
159             hiddenTypes.put(calcId, new ArrayList<List<String>>());
160           }
161           if (!hiddenTypes.get(calcId).contains(groupLabel))
162           {
163             hiddenTypes.get(calcId).add(groupLabel);
164           }
165         }
166       }
167     }
168   }
169
170   /**
171    * Returns a BitSet (possibly empty) of those graphGroups for line graph
172    * annotations, which have at least one member annotation row marked visible.
173    * <p/>
174    * Only one row in each visible group is marked visible, but when it is drawn,
175    * so are all the other rows in the same group.
176    * <p/>
177    * This lookup set allows us to check whether rows apparently marked not
178    * visible are in fact shown.
179    * 
180    * @see AnnotationRenderer#drawComponent
181    * @param annotations
182    * @return
183    */
184   public static BitSet getVisibleLineGraphGroups(
185           List<AlignmentAnnotation> annotations)
186   {
187     BitSet result = new BitSet();
188     for (AlignmentAnnotation ann : annotations)
189     {
190       if (ann.graph == AlignmentAnnotation.LINE_GRAPH && ann.visible)
191       {
192         int gg = ann.graphGroup;
193         if (gg > -1)
194         {
195           result.set(gg);
196         }
197       }
198     }
199     return result;
200   }
201
202   /**
203    * Converts an array of AlignmentAnnotation into a List of
204    * AlignmentAnnotation. A null array is converted to an empty list.
205    * 
206    * @param anns
207    * @return
208    */
209   public static List<AlignmentAnnotation> asList(AlignmentAnnotation[] anns)
210   {
211     // TODO use AlignmentAnnotationI instead when it exists
212     return (anns == null ? Collections.<AlignmentAnnotation> emptyList()
213             : Arrays.asList(anns));
214   }
215 }