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