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