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