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