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