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