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