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