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