JAL-1683 replace year/version strings with tokens in source
[jalview.git] / src / jalview / datamodel / ColumnSelection.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.datamodel;
22
23 import jalview.util.ShiftList;
24 import jalview.viewmodel.annotationfilter.AnnotationFilterParameter;
25 import jalview.viewmodel.annotationfilter.AnnotationFilterParameter.SearchableAnnotationField;
26
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Enumeration;
30 import java.util.List;
31 import java.util.Vector;
32
33 /**
34  * NOTE: Columns are zero based.
35  */
36 public class ColumnSelection
37 {
38   Vector selected = new Vector();
39
40   // Vector of int [] {startCol, endCol}
41   Vector<int[]> hiddenColumns;
42
43   /**
44    * Add a column to the selection
45    * 
46    * @param col
47    *          index of column
48    */
49   public void addElement(int col)
50   {
51     Integer column = new Integer(col);
52     if (!selected.contains(column))
53     {
54       selected.addElement(column);
55     }
56   }
57
58   /**
59    * clears column selection
60    */
61   public void clear()
62   {
63     selected.removeAllElements();
64   }
65
66   /**
67    * removes col from selection
68    * 
69    * @param col
70    *          index of column to be removed
71    */
72   public void removeElement(int col)
73   {
74     Integer colInt = new Integer(col);
75
76     if (selected.contains(colInt))
77     {
78       selected.removeElement(colInt);
79     }
80   }
81
82   /**
83    * removes a range of columns from the selection
84    * 
85    * @param start
86    *          int - first column in range to be removed
87    * @param end
88    *          int - last col
89    */
90   public void removeElements(int start, int end)
91   {
92     Integer colInt;
93     for (int i = start; i < end; i++)
94     {
95       colInt = new Integer(i);
96       if (selected.contains(colInt))
97       {
98         selected.removeElement(colInt);
99       }
100     }
101   }
102
103   /**
104    * 
105    * @return Vector containing selected columns as Integers
106    */
107   public Vector<Integer> getSelected()
108   {
109     return selected;
110   }
111
112   /**
113    * 
114    * @param col
115    *          index to search for in column selection
116    * 
117    * @return true if Integer(col) is in selection.
118    */
119   public boolean contains(int col)
120   {
121     return selected.contains(new Integer(col));
122   }
123
124   /**
125    * Column number at position i in selection
126    * 
127    * @param i
128    *          index into selected columns
129    * 
130    * @return column number in alignment
131    */
132   public int columnAt(int i)
133   {
134     return ((Integer) selected.elementAt(i)).intValue();
135   }
136
137   /**
138    * DOCUMENT ME!
139    * 
140    * @return DOCUMENT ME!
141    */
142   public int size()
143   {
144     return selected.size();
145   }
146
147   /**
148    * rightmost selected column
149    * 
150    * @return rightmost column in alignment that is selected
151    */
152   public int getMax()
153   {
154     int max = -1;
155
156     for (int i = 0; i < selected.size(); i++)
157     {
158       if (columnAt(i) > max)
159       {
160         max = columnAt(i);
161       }
162     }
163
164     return max;
165   }
166
167   /**
168    * Leftmost column in selection
169    * 
170    * @return column index of leftmost column in selection
171    */
172   public int getMin()
173   {
174     int min = 1000000000;
175
176     for (int i = 0; i < selected.size(); i++)
177     {
178       if (columnAt(i) < min)
179       {
180         min = columnAt(i);
181       }
182     }
183
184     return min;
185   }
186
187   /**
188    * propagate shift in alignment columns to column selection
189    * 
190    * @param start
191    *          beginning of edit
192    * @param left
193    *          shift in edit (+ve for removal, or -ve for inserts)
194    */
195   public List<int[]> compensateForEdit(int start, int change)
196   {
197     List<int[]> deletedHiddenColumns = null;
198     for (int i = 0; i < size(); i++)
199     {
200       int temp = columnAt(i);
201
202       if (temp >= start)
203       {
204         selected.setElementAt(new Integer(temp - change), i);
205       }
206     }
207
208     if (hiddenColumns != null)
209     {
210       deletedHiddenColumns = new ArrayList<int[]>();
211       int hSize = hiddenColumns.size();
212       for (int i = 0; i < hSize; i++)
213       {
214         int[] region = hiddenColumns.elementAt(i);
215         if (region[0] > start && start + change > region[1])
216         {
217           deletedHiddenColumns.add(region);
218
219           hiddenColumns.removeElementAt(i);
220           i--;
221           hSize--;
222           continue;
223         }
224
225         if (region[0] > start)
226         {
227           region[0] -= change;
228           region[1] -= change;
229         }
230
231         if (region[0] < 0)
232         {
233           region[0] = 0;
234         }
235
236       }
237
238       this.revealHiddenColumns(0);
239     }
240
241     return deletedHiddenColumns;
242   }
243
244   /**
245    * propagate shift in alignment columns to column selection special version of
246    * compensateForEdit - allowing for edits within hidden regions
247    * 
248    * @param start
249    *          beginning of edit
250    * @param left
251    *          shift in edit (+ve for removal, or -ve for inserts)
252    */
253   private void compensateForDelEdits(int start, int change)
254   {
255     for (int i = 0; i < size(); i++)
256     {
257       int temp = columnAt(i);
258
259       if (temp >= start)
260       {
261         selected.setElementAt(new Integer(temp - change), i);
262       }
263     }
264
265     if (hiddenColumns != null)
266     {
267       for (int i = 0; i < hiddenColumns.size(); i++)
268       {
269         int[] region = hiddenColumns.elementAt(i);
270         if (region[0] >= start)
271         {
272           region[0] -= change;
273         }
274         if (region[1] >= start)
275         {
276           region[1] -= change;
277         }
278         if (region[1] < region[0])
279         {
280           hiddenColumns.removeElementAt(i--);
281         }
282
283         if (region[0] < 0)
284         {
285           region[0] = 0;
286         }
287         if (region[1] < 0)
288         {
289           region[1] = 0;
290         }
291       }
292     }
293   }
294
295   /**
296    * Adjust hidden column boundaries based on a series of column additions or
297    * deletions in visible regions.
298    * 
299    * @param shiftrecord
300    * @return
301    */
302   public ShiftList compensateForEdits(ShiftList shiftrecord)
303   {
304     if (shiftrecord != null)
305     {
306       Vector shifts = shiftrecord.shifts;
307       if (shifts != null && shifts.size() > 0)
308       {
309         int shifted = 0;
310         for (int i = 0, j = shifts.size(); i < j; i++)
311         {
312           int[] sh = (int[]) shifts.elementAt(i);
313           // compensateForEdit(shifted+sh[0], sh[1]);
314           compensateForDelEdits(shifted + sh[0], sh[1]);
315           shifted -= sh[1];
316         }
317       }
318       return shiftrecord.getInverse();
319     }
320     return null;
321   }
322
323   /**
324    * removes intersection of position,length ranges in deletions from the
325    * start,end regions marked in intervals.
326    * 
327    * @param deletions
328    * @param intervals
329    * @return
330    */
331   private boolean pruneIntervalVector(Vector deletions, Vector intervals)
332   {
333     boolean pruned = false;
334     int i = 0, j = intervals.size() - 1, s = 0, t = deletions.size() - 1;
335     int hr[] = (int[]) intervals.elementAt(i);
336     int sr[] = (int[]) deletions.elementAt(s);
337     while (i <= j && s <= t)
338     {
339       boolean trailinghn = hr[1] >= sr[0];
340       if (!trailinghn)
341       {
342         if (i < j)
343         {
344           hr = (int[]) intervals.elementAt(++i);
345         }
346         else
347         {
348           i++;
349         }
350         continue;
351       }
352       int endshift = sr[0] + sr[1]; // deletion ranges - -ve means an insert
353       if (endshift < hr[0] || endshift < sr[0])
354       { // leadinghc disjoint or not a deletion
355         if (s < t)
356         {
357           sr = (int[]) deletions.elementAt(++s);
358         }
359         else
360         {
361           s++;
362         }
363         continue;
364       }
365       boolean leadinghn = hr[0] >= sr[0];
366       boolean leadinghc = hr[0] < endshift;
367       boolean trailinghc = hr[1] < endshift;
368       if (leadinghn)
369       {
370         if (trailinghc)
371         { // deleted hidden region.
372           intervals.removeElementAt(i);
373           pruned = true;
374           j--;
375           if (i <= j)
376           {
377             hr = (int[]) intervals.elementAt(i);
378           }
379           continue;
380         }
381         if (leadinghc)
382         {
383           hr[0] = endshift; // clip c terminal region
384           leadinghn = !leadinghn;
385           pruned = true;
386         }
387       }
388       if (!leadinghn)
389       {
390         if (trailinghc)
391         {
392           if (trailinghn)
393           {
394             hr[1] = sr[0] - 1;
395             pruned = true;
396           }
397         }
398         else
399         {
400           // sr contained in hr
401           if (s < t)
402           {
403             sr = (int[]) deletions.elementAt(++s);
404           }
405           else
406           {
407             s++;
408           }
409           continue;
410         }
411       }
412     }
413     return pruned; // true if any interval was removed or modified by
414     // operations.
415   }
416
417   private boolean pruneColumnList(Vector deletion, Vector list)
418   {
419     int s = 0, t = deletion.size();
420     int[] sr = (int[]) list.elementAt(s++);
421     boolean pruned = false;
422     int i = 0, j = list.size();
423     while (i < j && s <= t)
424     {
425       int c = ((Integer) list.elementAt(i++)).intValue();
426       if (sr[0] <= c)
427       {
428         if (sr[1] + sr[0] >= c)
429         { // sr[1] -ve means inseriton.
430           list.removeElementAt(--i);
431           j--;
432         }
433         else
434         {
435           if (s < t)
436           {
437             sr = (int[]) deletion.elementAt(s);
438           }
439           s++;
440         }
441       }
442     }
443     return pruned;
444   }
445
446   /**
447    * remove any hiddenColumns or selected columns and shift remaining based on a
448    * series of position, range deletions.
449    * 
450    * @param deletions
451    */
452   public void pruneDeletions(ShiftList deletions)
453   {
454     if (deletions != null)
455     {
456       Vector shifts = deletions.shifts;
457       if (shifts != null && shifts.size() > 0)
458       {
459         // delete any intervals intersecting.
460         if (hiddenColumns != null)
461         {
462           pruneIntervalVector(shifts, hiddenColumns);
463           if (hiddenColumns != null && hiddenColumns.size() == 0)
464           {
465             hiddenColumns = null;
466           }
467         }
468         if (selected != null && selected.size() > 0)
469         {
470           pruneColumnList(shifts, selected);
471           if (selected != null && selected.size() == 0)
472           {
473             selected = null;
474           }
475         }
476         // and shift the rest.
477         this.compensateForEdits(deletions);
478       }
479     }
480   }
481
482   /**
483    * This Method is used to return all the HiddenColumn regions
484    * @return empty list or List of hidden column intervals
485    */
486   public List<int[]> getHiddenColumns()
487   {
488     return hiddenColumns == null ? Arrays.asList(new int[]
489     {}) : hiddenColumns;
490   }
491
492   /**
493    * Return absolute column index for a visible column index
494    * 
495    * @param column
496    *          int column index in alignment view
497    * @return alignment column index for column
498    */
499   public int adjustForHiddenColumns(int column)
500   {
501     int result = column;
502     if (hiddenColumns != null)
503     {
504       for (int i = 0; i < hiddenColumns.size(); i++)
505       {
506         int[] region = hiddenColumns.elementAt(i);
507         if (result >= region[0])
508         {
509           result += region[1] - region[0] + 1;
510         }
511       }
512     }
513     return result;
514   }
515
516   /**
517    * Use this method to find out where a column will appear in the visible
518    * alignment when hidden columns exist. If the column is not visible, then the
519    * left-most visible column will always be returned.
520    * 
521    * @param hiddenColumn
522    *          int
523    * @return int
524    */
525   public int findColumnPosition(int hiddenColumn)
526   {
527     int result = hiddenColumn;
528     if (hiddenColumns != null)
529     {
530       int index = 0;
531       int[] region;
532       do
533       {
534         region = hiddenColumns.elementAt(index++);
535         if (hiddenColumn > region[1])
536         {
537           result -= region[1] + 1 - region[0];
538         }
539       } while ((hiddenColumn > region[1]) && (index < hiddenColumns.size()));
540       if (hiddenColumn > region[0] && hiddenColumn < region[1])
541       {
542         return region[0] + hiddenColumn - result;
543       }
544     }
545     return result; // return the shifted position after removing hidden columns.
546   }
547
548   /**
549    * Use this method to determine where the next hiddenRegion starts
550    */
551   public int findHiddenRegionPosition(int hiddenRegion)
552   {
553     int result = 0;
554     if (hiddenColumns != null)
555     {
556       int index = 0;
557       int gaps = 0;
558       do
559       {
560         int[] region = hiddenColumns.elementAt(index);
561         if (hiddenRegion == 0)
562         {
563           return region[0];
564         }
565
566         gaps += region[1] + 1 - region[0];
567         result = region[1] + 1;
568         index++;
569       } while (index < hiddenRegion + 1);
570
571       result -= gaps;
572     }
573
574     return result;
575   }
576
577   /**
578    * THis method returns the rightmost limit of a region of an alignment with
579    * hidden columns. In otherwords, the next hidden column.
580    * 
581    * @param index
582    *          int
583    */
584   public int getHiddenBoundaryRight(int alPos)
585   {
586     if (hiddenColumns != null)
587     {
588       int index = 0;
589       do
590       {
591         int[] region = hiddenColumns.elementAt(index);
592         if (alPos < region[0])
593         {
594           return region[0];
595         }
596
597         index++;
598       } while (index < hiddenColumns.size());
599     }
600
601     return alPos;
602
603   }
604
605   /**
606    * This method returns the leftmost limit of a region of an alignment with
607    * hidden columns. In otherwords, the previous hidden column.
608    * 
609    * @param index
610    *          int
611    */
612   public int getHiddenBoundaryLeft(int alPos)
613   {
614     if (hiddenColumns != null)
615     {
616       int index = hiddenColumns.size() - 1;
617       do
618       {
619         int[] region = hiddenColumns.elementAt(index);
620         if (alPos > region[1])
621         {
622           return region[1];
623         }
624
625         index--;
626       } while (index > -1);
627     }
628
629     return alPos;
630
631   }
632
633   public void hideSelectedColumns()
634   {
635     while (size() > 0)
636     {
637       int column = getSelected().firstElement().intValue();
638       hideColumns(column);
639     }
640
641   }
642
643   public void hideColumns(int start, int end)
644   {
645     if (hiddenColumns == null)
646     {
647       hiddenColumns = new Vector();
648     }
649
650     boolean added = false;
651     boolean overlap = false;
652
653     for (int i = 0; i < hiddenColumns.size(); i++)
654     {
655       int[] region = hiddenColumns.elementAt(i);
656       if (start <= region[1] && end >= region[0])
657       {
658         hiddenColumns.removeElementAt(i);
659         overlap = true;
660         break;
661       }
662       else if (end < region[0] && start < region[0])
663       {
664         hiddenColumns.insertElementAt(new int[]
665         { start, end }, i);
666         added = true;
667         break;
668       }
669     }
670
671     if (overlap)
672     {
673       hideColumns(start, end);
674     }
675     else if (!added)
676     {
677       hiddenColumns.addElement(new int[]
678       { start, end });
679     }
680
681   }
682
683   /**
684    * This method will find a range of selected columns around the column
685    * specified
686    * 
687    * @param res
688    *          int
689    */
690   public void hideColumns(int col)
691   {
692     // First find out range of columns to hide
693     int min = col, max = col + 1;
694     while (contains(min))
695     {
696       removeElement(min);
697       min--;
698     }
699
700     while (contains(max))
701     {
702       removeElement(max);
703       max++;
704     }
705
706     min++;
707     max--;
708     if (min > max)
709     {
710       min = max;
711     }
712
713     hideColumns(min, max);
714   }
715
716   public void revealAllHiddenColumns()
717   {
718     if (hiddenColumns != null)
719     {
720       for (int i = 0; i < hiddenColumns.size(); i++)
721       {
722         int[] region = hiddenColumns.elementAt(i);
723         for (int j = region[0]; j < region[1] + 1; j++)
724         {
725           addElement(j);
726         }
727       }
728     }
729
730     hiddenColumns = null;
731   }
732
733   public void revealHiddenColumns(int res)
734   {
735     for (int i = 0; i < hiddenColumns.size(); i++)
736     {
737       int[] region = hiddenColumns.elementAt(i);
738       if (res == region[0])
739       {
740         for (int j = region[0]; j < region[1] + 1; j++)
741         {
742           addElement(j);
743         }
744
745         hiddenColumns.removeElement(region);
746         break;
747       }
748     }
749     if (hiddenColumns.size() == 0)
750     {
751       hiddenColumns = null;
752     }
753   }
754
755   public boolean isVisible(int column)
756   {
757     if (hiddenColumns != null)
758     {
759       for (int i = 0; i < hiddenColumns.size(); i++)
760       {
761         int[] region = hiddenColumns.elementAt(i);
762         if (column >= region[0] && column <= region[1])
763         {
764           return false;
765         }
766       }
767     }
768
769     return true;
770   }
771
772   /**
773    * Copy constructor
774    * 
775    * @param copy
776    */
777   public ColumnSelection(ColumnSelection copy)
778   {
779     if (copy != null)
780     {
781       if (copy.selected != null)
782       {
783         selected = new Vector();
784         for (int i = 0, j = copy.selected.size(); i < j; i++)
785         {
786           selected.addElement(copy.selected.elementAt(i));
787         }
788       }
789       if (copy.hiddenColumns != null)
790       {
791         hiddenColumns = new Vector(copy.hiddenColumns.size());
792         for (int i = 0, j = copy.hiddenColumns.size(); i < j; i++)
793         {
794           int[] rh, cp;
795           rh = copy.hiddenColumns.elementAt(i);
796           if (rh != null)
797           {
798             cp = new int[rh.length];
799             System.arraycopy(rh, 0, cp, 0, rh.length);
800             hiddenColumns.addElement(cp);
801           }
802         }
803       }
804     }
805   }
806
807   /**
808    * ColumnSelection
809    */
810   public ColumnSelection()
811   {
812   }
813
814   public String[] getVisibleSequenceStrings(int start, int end,
815           SequenceI[] seqs)
816   {
817     int i, iSize = seqs.length;
818     String selection[] = new String[iSize];
819     if (hiddenColumns != null && hiddenColumns.size() > 0)
820     {
821       for (i = 0; i < iSize; i++)
822       {
823         StringBuffer visibleSeq = new StringBuffer();
824         List<int[]> regions = getHiddenColumns();
825
826         int blockStart = start, blockEnd = end;
827         int[] region;
828         int hideStart, hideEnd;
829
830         for (int j = 0; j < regions.size(); j++)
831         {
832           region = regions.get(j);
833           hideStart = region[0];
834           hideEnd = region[1];
835
836           if (hideStart < start)
837           {
838             continue;
839           }
840
841           blockStart = Math.min(blockStart, hideEnd + 1);
842           blockEnd = Math.min(blockEnd, hideStart);
843
844           if (blockStart > blockEnd)
845           {
846             break;
847           }
848
849           visibleSeq.append(seqs[i].getSequence(blockStart, blockEnd));
850
851           blockStart = hideEnd + 1;
852           blockEnd = end;
853         }
854
855         if (end > blockStart)
856         {
857           visibleSeq.append(seqs[i].getSequence(blockStart, end));
858         }
859
860         selection[i] = visibleSeq.toString();
861       }
862     }
863     else
864     {
865       for (i = 0; i < iSize; i++)
866       {
867         selection[i] = seqs[i].getSequenceAsString(start, end);
868       }
869     }
870
871     return selection;
872   }
873
874   /**
875    * return all visible segments between the given start and end boundaries
876    * 
877    * @param start
878    *          (first column inclusive from 0)
879    * @param end
880    *          (last column - not inclusive)
881    * @return int[] {i_start, i_end, ..} where intervals lie in
882    *         start<=i_start<=i_end<end
883    */
884   public int[] getVisibleContigs(int start, int end)
885   {
886     if (hiddenColumns != null && hiddenColumns.size() > 0)
887     {
888       Vector visiblecontigs = new Vector();
889       List<int[]> regions = getHiddenColumns();
890
891       int vstart = start;
892       int[] region;
893       int hideStart, hideEnd;
894
895       for (int j = 0; vstart < end && j < regions.size(); j++)
896       {
897         region = regions.get(j);
898         hideStart = region[0];
899         hideEnd = region[1];
900
901         if (hideEnd < vstart)
902         {
903           continue;
904         }
905         if (hideStart > vstart)
906         {
907           visiblecontigs.addElement(new int[]
908           { vstart, hideStart - 1 });
909         }
910         vstart = hideEnd + 1;
911       }
912
913       if (vstart < end)
914       {
915         visiblecontigs.addElement(new int[]
916         { vstart, end - 1 });
917       }
918       int[] vcontigs = new int[visiblecontigs.size() * 2];
919       for (int i = 0, j = visiblecontigs.size(); i < j; i++)
920       {
921         int[] vc = (int[]) visiblecontigs.elementAt(i);
922         visiblecontigs.setElementAt(null, i);
923         vcontigs[i * 2] = vc[0];
924         vcontigs[i * 2 + 1] = vc[1];
925       }
926       visiblecontigs.removeAllElements();
927       return vcontigs;
928     }
929     else
930     {
931       return new int[]
932       { start, end - 1 };
933     }
934   }
935
936   /**
937    * delete any columns in alignmentAnnotation that are hidden (including
938    * sequence associated annotation).
939    * 
940    * @param alignmentAnnotation
941    */
942   public void makeVisibleAnnotation(AlignmentAnnotation alignmentAnnotation)
943   {
944     makeVisibleAnnotation(-1, -1, alignmentAnnotation);
945   }
946
947   /**
948    * delete any columns in alignmentAnnotation that are hidden (including
949    * sequence associated annotation).
950    * 
951    * @param start
952    *          remove any annotation to the right of this column
953    * @param end
954    *          remove any annotation to the left of this column
955    * @param alignmentAnnotation
956    *          the annotation to operate on
957    */
958   public void makeVisibleAnnotation(int start, int end,
959           AlignmentAnnotation alignmentAnnotation)
960   {
961     if (alignmentAnnotation.annotations == null)
962     {
963       return;
964     }
965     if (start == end && end == -1)
966     {
967       start = 0;
968       end = alignmentAnnotation.annotations.length;
969     }
970     if (hiddenColumns != null && hiddenColumns.size() > 0)
971     {
972       // then mangle the alignmentAnnotation annotation array
973       Vector annels = new Vector();
974       Annotation[] els = null;
975       List<int[]> regions = getHiddenColumns();
976       int blockStart = start, blockEnd = end;
977       int[] region;
978       int hideStart, hideEnd, w = 0;
979
980       for (int j = 0; j < regions.size(); j++)
981       {
982         region = regions.get(j);
983         hideStart = region[0];
984         hideEnd = region[1];
985
986         if (hideStart < start)
987         {
988           continue;
989         }
990
991         blockStart = Math.min(blockStart, hideEnd + 1);
992         blockEnd = Math.min(blockEnd, hideStart);
993
994         if (blockStart > blockEnd)
995         {
996           break;
997         }
998
999         annels.addElement(els = new Annotation[blockEnd - blockStart]);
1000         System.arraycopy(alignmentAnnotation.annotations, blockStart, els,
1001                 0, els.length);
1002         w += els.length;
1003         blockStart = hideEnd + 1;
1004         blockEnd = end;
1005       }
1006
1007       if (end > blockStart)
1008       {
1009         annels.addElement(els = new Annotation[end - blockStart + 1]);
1010         if ((els.length + blockStart) <= alignmentAnnotation.annotations.length)
1011         {
1012           // copy just the visible segment of the annotation row
1013           System.arraycopy(alignmentAnnotation.annotations, blockStart,
1014                   els, 0, els.length);
1015         }
1016         else
1017         {
1018           // copy to the end of the annotation row
1019           System.arraycopy(alignmentAnnotation.annotations, blockStart,
1020                   els, 0,
1021                   (alignmentAnnotation.annotations.length - blockStart));
1022         }
1023         w += els.length;
1024       }
1025       if (w == 0)
1026       {
1027         return;
1028       }
1029       Enumeration e = annels.elements();
1030       alignmentAnnotation.annotations = new Annotation[w];
1031       w = 0;
1032       while (e.hasMoreElements())
1033       {
1034         Annotation[] chnk = (Annotation[]) e.nextElement();
1035         System.arraycopy(chnk, 0, alignmentAnnotation.annotations, w,
1036                 chnk.length);
1037         w += chnk.length;
1038       }
1039     }
1040     else
1041     {
1042       alignmentAnnotation.restrict(start, end);
1043     }
1044   }
1045
1046   /**
1047    * Invert the column selection from first to end-1. leaves hiddenColumns
1048    * untouched (and unselected)
1049    * 
1050    * @param first
1051    * @param end
1052    */
1053   public void invertColumnSelection(int first, int width)
1054   {
1055     boolean hasHidden = hiddenColumns != null && hiddenColumns.size() > 0;
1056     for (int i = first; i < width; i++)
1057     {
1058       if (contains(i))
1059       {
1060         removeElement(i);
1061       }
1062       else
1063       {
1064         if (!hasHidden || isVisible(i))
1065         {
1066           addElement(i);
1067         }
1068       }
1069     }
1070   }
1071
1072   /**
1073    * add in any unselected columns from the given column selection, excluding
1074    * any that are hidden.
1075    * 
1076    * @param colsel
1077    */
1078   public void addElementsFrom(ColumnSelection colsel)
1079   {
1080     if (colsel != null && colsel.size() > 0)
1081     {
1082       Enumeration e = colsel.getSelected().elements();
1083       while (e.hasMoreElements())
1084       {
1085         Object eo = e.nextElement();
1086         if (hiddenColumns != null && isVisible(((Integer) eo).intValue()))
1087         {
1088           if (!selected.contains(eo))
1089           {
1090             selected.addElement(eo);
1091           }
1092         }
1093       }
1094     }
1095   }
1096
1097   /**
1098    * set the selected columns the given column selection, excluding any columns
1099    * that are hidden.
1100    * 
1101    * @param colsel
1102    */
1103   public void setElementsFrom(ColumnSelection colsel)
1104   {
1105     selected = new Vector();
1106     if (colsel.selected != null && colsel.selected.size() > 0)
1107     {
1108       if (hiddenColumns != null && hiddenColumns.size() > 0)
1109       {
1110         // only select visible columns in this columns selection
1111         selected = new Vector();
1112         addElementsFrom(colsel);
1113       }
1114       else
1115       {
1116         // add everything regardless
1117         Enumeration en = colsel.selected.elements();
1118         while (en.hasMoreElements())
1119         {
1120           selected.addElement(en.nextElement());
1121         }
1122       }
1123     }
1124   }
1125
1126   /**
1127    * Add gaps into the sequences aligned to profileseq under the given
1128    * AlignmentView
1129    * 
1130    * @param profileseq
1131    * @param al
1132    *          - alignment to have gaps inserted into it
1133    * @param input
1134    *          - alignment view where sequence corresponding to profileseq is
1135    *          first entry
1136    * @return new Column selection for new alignment view, with insertions into
1137    *         profileseq marked as hidden.
1138    */
1139   public static ColumnSelection propagateInsertions(SequenceI profileseq,
1140           Alignment al, AlignmentView input)
1141   {
1142     int profsqpos = 0;
1143
1144     // return propagateInsertions(profileseq, al, )
1145     char gc = al.getGapCharacter();
1146     Object[] alandcolsel = input.getAlignmentAndColumnSelection(gc);
1147     ColumnSelection nview = (ColumnSelection) alandcolsel[1];
1148     SequenceI origseq = ((SequenceI[]) alandcolsel[0])[profsqpos];
1149     nview.propagateInsertions(profileseq, al, origseq);
1150     return nview;
1151   }
1152
1153   /**
1154    * 
1155    * @param profileseq
1156    *          - sequence in al which corresponds to origseq
1157    * @param al
1158    *          - alignment which is to have gaps inserted into it
1159    * @param origseq
1160    *          - sequence corresponding to profileseq which defines gap map for
1161    *          modifying al
1162    */
1163   public void propagateInsertions(SequenceI profileseq, AlignmentI al,
1164           SequenceI origseq)
1165   {
1166     char gc = al.getGapCharacter();
1167     // recover mapping between sequence's non-gap positions and positions
1168     // mapping to view.
1169     pruneDeletions(ShiftList.parseMap(origseq.gapMap()));
1170     int[] viscontigs = getVisibleContigs(0, profileseq.getLength());
1171     int spos = 0;
1172     int offset = 0;
1173     // input.pruneDeletions(ShiftList.parseMap(((SequenceI[])
1174     // alandcolsel[0])[0].gapMap()))
1175     // add profile to visible contigs
1176     for (int v = 0; v < viscontigs.length; v += 2)
1177     {
1178       if (viscontigs[v] > spos)
1179       {
1180         StringBuffer sb = new StringBuffer();
1181         for (int s = 0, ns = viscontigs[v] - spos; s < ns; s++)
1182         {
1183           sb.append(gc);
1184         }
1185         for (int s = 0, ns = al.getHeight(); s < ns; s++)
1186         {
1187           SequenceI sqobj = al.getSequenceAt(s);
1188           if (sqobj != profileseq)
1189           {
1190             String sq = al.getSequenceAt(s).getSequenceAsString();
1191             if (sq.length() <= spos + offset)
1192             {
1193               // pad sequence
1194               int diff = spos + offset - sq.length() - 1;
1195               if (diff > 0)
1196               {
1197                 // pad gaps
1198                 sq = sq + sb;
1199                 while ((diff = spos + offset - sq.length() - 1) > 0)
1200                 {
1201                   // sq = sq
1202                   // + ((diff >= sb.length()) ? sb.toString() : sb
1203                   // .substring(0, diff));
1204                   if (diff >= sb.length())
1205                   {
1206                     sq += sb.toString();
1207                   }
1208                   else
1209                   {
1210                     char[] buf = new char[diff];
1211                     sb.getChars(0, diff, buf, 0);
1212                     sq += buf.toString();
1213                   }
1214                 }
1215               }
1216               sq += sb.toString();
1217             }
1218             else
1219             {
1220               al.getSequenceAt(s).setSequence(
1221                       sq.substring(0, spos + offset) + sb.toString()
1222                               + sq.substring(spos + offset));
1223             }
1224           }
1225         }
1226         // offset+=sb.length();
1227       }
1228       spos = viscontigs[v + 1] + 1;
1229     }
1230     if ((offset + spos) < profileseq.getLength())
1231     {
1232       // pad the final region with gaps.
1233       StringBuffer sb = new StringBuffer();
1234       for (int s = 0, ns = profileseq.getLength() - spos - offset; s < ns; s++)
1235       {
1236         sb.append(gc);
1237       }
1238       for (int s = 0, ns = al.getHeight(); s < ns; s++)
1239       {
1240         SequenceI sqobj = al.getSequenceAt(s);
1241         if (sqobj == profileseq)
1242         {
1243           continue;
1244         }
1245         String sq = sqobj.getSequenceAsString();
1246         // pad sequence
1247         int diff = origseq.getLength() - sq.length();
1248         while (diff > 0)
1249         {
1250           // sq = sq
1251           // + ((diff >= sb.length()) ? sb.toString() : sb
1252           // .substring(0, diff));
1253           if (diff >= sb.length())
1254           {
1255             sq += sb.toString();
1256           }
1257           else
1258           {
1259             char[] buf = new char[diff];
1260             sb.getChars(0, diff, buf, 0);
1261             sq += buf.toString();
1262           }
1263           diff = origseq.getLength() - sq.length();
1264         }
1265       }
1266     }
1267   }
1268
1269   /**
1270    * 
1271    * @return true if there are columns marked
1272    */
1273   public boolean hasSelectedColumns()
1274   {
1275     return (selected != null && selected.size() > 0);
1276   }
1277
1278   /**
1279    * 
1280    * @return true if there are columns hidden
1281    */
1282   public boolean hasHiddenColumns()
1283   {
1284     return hiddenColumns != null && hiddenColumns.size() > 0;
1285   }
1286   
1287   /**
1288    * 
1289    * @return true if there are more than one set of columns hidden
1290    */
1291   public boolean hasManyHiddenColumns()
1292   {
1293     return hiddenColumns != null && hiddenColumns.size() > 1;
1294   }
1295
1296   /**
1297    * mark the columns corresponding to gap characters as hidden in the column
1298    * selection
1299    * 
1300    * @param sr
1301    */
1302   public void hideInsertionsFor(SequenceI sr)
1303   {
1304     List<int[]> inserts = sr.getInsertions();
1305     for (int[] r : inserts)
1306     {
1307       hideColumns(r[0], r[1]);
1308     }
1309   }
1310   
1311   public boolean filterAnnotations(Annotation[] annotations,
1312           AnnotationFilterParameter filterParams)
1313   {
1314     this.revealAllHiddenColumns();
1315     this.clear();
1316     int count = 0;
1317     do
1318     {
1319       if (annotations[count] != null)
1320       {
1321
1322         boolean itemMatched = false;
1323
1324         if (filterParams.getThresholdType() == AnnotationFilterParameter.ThresholdType.ABOVE_THRESHOLD
1325                 && annotations[count].value >= filterParams
1326                         .getThresholdValue())
1327         {
1328           itemMatched = true;
1329         }
1330         if (filterParams.getThresholdType() == AnnotationFilterParameter.ThresholdType.BELOW_THRESHOLD
1331                 && annotations[count].value <= filterParams
1332                         .getThresholdValue())
1333         {
1334           itemMatched = true;
1335         }
1336
1337         if (filterParams.isFilterAlphaHelix()
1338                 && annotations[count].secondaryStructure == 'H')
1339         {
1340           itemMatched = true;
1341         }
1342
1343         if (filterParams.isFilterBetaSheet()
1344                 && annotations[count].secondaryStructure == 'E')
1345         {
1346           itemMatched = true;
1347         }
1348
1349         if (filterParams.isFilterTurn()
1350                 && annotations[count].secondaryStructure == 'S')
1351         {
1352           itemMatched = true;
1353         }
1354
1355         String regexSearchString = filterParams.getRegexString();
1356         if (regexSearchString != null
1357                 && !filterParams.getRegexSearchFields().isEmpty())
1358         {
1359           List<SearchableAnnotationField> fields = filterParams
1360                   .getRegexSearchFields();
1361           try
1362           {
1363             if (fields.contains(SearchableAnnotationField.DISPLAY_STRING)
1364                     && annotations[count].displayCharacter
1365                             .matches(regexSearchString))
1366             {
1367               itemMatched = true;
1368             }
1369           } catch (java.util.regex.PatternSyntaxException pse)
1370           {
1371             if (annotations[count].displayCharacter
1372                     .equals(regexSearchString))
1373             {
1374               itemMatched = true;
1375             }
1376           }
1377           if (fields.contains(SearchableAnnotationField.DESCRIPTION)
1378                   && annotations[count].description != null
1379                   && annotations[count].description
1380                           .matches(regexSearchString))
1381           {
1382             itemMatched = true;
1383           }
1384         }
1385
1386         if (itemMatched)
1387         {
1388           this.addElement(count);
1389         }
1390       }
1391       count++;
1392     } while (count < annotations.length);
1393     return false;
1394   }
1395 }