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