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