c895b2f3c8b2a1c604f416bf585dbac5ff09e9d8
[jalview.git] / src / jalview / datamodel / ColumnSelection.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer
3  * Copyright (C) 2006 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
18  */
19 package jalview.datamodel;
20
21 import jalview.util.ShiftList;
22
23 import java.util.*;
24
25 /**
26  * NOTE: Columns are zero based.
27  */
28 public class ColumnSelection
29 {
30   Vector selected = new Vector();
31
32   //Vector of int [] {startCol, endCol}
33   Vector hiddenColumns;
34
35   /**
36    * Add a column to the selection
37    *
38    * @param col 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 index of column to be removed
61    */
62   public void removeElement(int col)
63   {
64     Integer colInt = new Integer(col);
65
66     if (selected.contains(colInt))
67     {
68       selected.removeElement(colInt);
69     }
70   }
71
72   /**
73    * removes a range of columns from the selection
74    * @param start int - first column in range to be removed
75    * @param end int - last col
76    */
77   public void removeElements(int start, int end)
78   {
79     Integer colInt;
80     for(int i=start; i<end; i++)
81     {
82       colInt = new Integer(i);
83       if (selected.contains(colInt))
84       {
85         selected.removeElement(colInt);
86       }
87     }
88   }
89   /**
90    *
91    * @return Vector containing selected columns as Integers
92    */
93   public Vector getSelected()
94   {
95     return selected;
96   }
97
98   /**
99    *
100    * @param col index to search for in column selection
101    *
102    * @return true if Integer(col) is in selection.
103    */
104   public boolean contains(int col)
105   {
106     return selected.contains(new Integer(col));
107   }
108
109   /**
110    * DOCUMENT ME!
111    *
112    * @param i DOCUMENT ME!
113    *
114    * @return DOCUMENT ME!
115    */
116   public int columnAt(int i)
117   {
118     return ((Integer) selected.elementAt(i)).intValue();
119   }
120
121   /**
122    * DOCUMENT ME!
123    *
124    * @return DOCUMENT ME!
125    */
126   public int size()
127   {
128     return selected.size();
129   }
130
131   /**
132    * DOCUMENT ME!
133    *
134    * @return DOCUMENT ME!
135    */
136   public int getMax()
137   {
138     int max = -1;
139
140     for (int i = 0; i < selected.size(); i++)
141     {
142       if (columnAt(i) > max)
143       {
144         max = columnAt(i);
145       }
146     }
147
148     return max;
149   }
150
151   /**
152    * DOCUMENT ME!
153    *
154    * @return DOCUMENT ME!
155    */
156   public int getMin()
157   {
158     int min = 1000000000;
159
160     for (int i = 0; i < selected.size(); i++)
161     {
162       if (columnAt(i) < min)
163       {
164         min = columnAt(i);
165       }
166     }
167
168     return min;
169   }
170
171
172   /**
173    * propagate shift in alignment columns to column selection
174    *
175    * @param start beginning of edit
176    * @param left shift in edit (+ve for removal, or -ve for inserts)
177    */
178   public void compensateForEdit(int start, int change)
179   {
180     for (int i = 0; i < size(); i++)
181     {
182       int temp = columnAt(i);
183
184       if (temp >= start)
185       {
186         selected.setElementAt(new Integer(temp - change), i);
187       }
188     }
189
190     if(hiddenColumns!=null)
191     {
192       for(int i=0; i<hiddenColumns.size(); i++)
193       {
194         int[] region = (int[]) hiddenColumns.elementAt(i);
195         if(region[0] > start)
196         {
197           region[0] -= change;
198           region[1] -= change;
199         }
200         if(region[0]<0)
201           region[0] = 0;
202         if(region[1] <0)
203           region[1] = 0;
204       }
205     }
206   }
207   /**
208    * propagate shift in alignment columns to column selection
209    * special version of compensateForEdit - allowing for edits within hidden regions
210    * @param start beginning of edit
211    * @param left shift in edit (+ve for removal, or -ve for inserts)
212    */
213   private void compensateForDelEdits(int start, int change)
214   {
215     for (int i = 0; i < size(); i++)
216     {
217       int temp = columnAt(i);
218
219       if (temp >= start)
220       {
221         selected.setElementAt(new Integer(temp - change), i);
222       }
223     }
224
225     if(hiddenColumns!=null)
226     {
227       for(int i=0; i<hiddenColumns.size(); i++)
228       {
229         int[] region = (int[]) hiddenColumns.elementAt(i);
230         if(region[0] >= start)
231         {
232           region[0] -= change;
233         }
234         if (region[1]>= start) {
235           region[1] -=change;
236         }
237         if (region[1]<region[0]) {
238           hiddenColumns.removeElementAt(i--);
239         }
240
241         if(region[0]<0)
242           region[0] = 0;
243         if(region[1] <0)
244           region[1] = 0;
245       }
246     }
247   }
248   /**
249    * Adjust hidden column boundaries based on a series of column
250    * additions or deletions in visible regions.
251    * @param shiftrecord
252    * @return
253    */
254   public ShiftList compensateForEdits(ShiftList shiftrecord) {
255     if (shiftrecord!=null) {
256       Vector shifts = shiftrecord.shifts;
257       if (shifts!=null && shifts.size()>0) {
258         int shifted=0;
259         for (int i=0,j=shifts.size(); i<j; i++) {
260           int[] sh = (int[]) shifts.elementAt(i);
261           //compensateForEdit(shifted+sh[0], sh[1]);
262           compensateForDelEdits(shifted+sh[0], sh[1]);
263           shifted-=sh[1];
264         }
265       }
266       return shiftrecord.getInverse();
267     }
268     return null;
269   }
270   /**
271    * removes intersection of position,length ranges in deletions
272    * from the start,end regions marked in intervals.
273    * @param deletions
274    * @param intervals
275    * @return
276    */
277   private boolean pruneIntervalVector(Vector deletions, Vector intervals) {
278     boolean pruned=false;
279     int i=0,j=intervals.size()-1, s=0, t=deletions.size()-1;
280     int hr[]=(int[]) intervals.elementAt(i);
281     int sr[]=(int[]) deletions.elementAt(s);
282     while (i<=j && s<=t) {
283       boolean trailinghn=hr[1]>=sr[0];
284       if (!trailinghn) {
285         if (i<j)
286           hr=(int[]) intervals.elementAt(++i);
287         else
288           i++;
289         continue;
290       }
291       int endshift=sr[0]+sr[1]; // deletion ranges - -ve means an insert
292       if (endshift<hr[0] || endshift<sr[0]) { // leadinghc disjoint or not a deletion
293         if (s<t)
294           sr=(int[]) deletions.elementAt(++s);
295         else
296           s++;
297         continue;
298       }
299       boolean leadinghn=hr[0]>=sr[0];
300       boolean leadinghc=hr[0]<endshift;
301       boolean trailinghc=hr[1]<endshift;
302       if (leadinghn) {
303         if (trailinghc) {// deleted hidden region.
304           intervals.removeElementAt(i);
305           pruned=true;
306           j--;
307           if (i<=j)
308             hr=(int[]) intervals.elementAt(i);
309           continue;
310         }
311         if (leadinghc) {
312           hr[0]=endshift; // clip c terminal region
313           leadinghn=!leadinghn;
314           pruned=true;
315         }
316       }
317       if (!leadinghn) {
318         if (trailinghc) {
319           if (trailinghn) {
320             hr[1]=sr[0]-1;
321             pruned=true;
322           }
323         } else {
324           // sr contained in hr
325           if (s<t)
326             sr=(int[]) deletions.elementAt(++s);
327           else
328             s++;
329           continue;
330         }
331       }
332     }
333     return pruned; // true if any interval was removed or modified by operations.
334   }
335   private boolean pruneColumnList(Vector deletion, Vector list) {
336     int s=0,t=deletion.size();
337     int[] sr=(int[])list.elementAt(s++);
338     boolean pruned=false;
339     int i=0, j=list.size();
340     while (i<j && s<=t) {
341       int c=((Integer)list.elementAt(i++)).intValue();
342       if (sr[0]<=c) {
343         if (sr[1]+sr[0]>=c) { // sr[1] -ve means inseriton.
344           list.removeElementAt(--i);
345           j--;
346         } else {
347           if (s<t)
348             sr = (int[])deletion.elementAt(s);
349           s++;
350         }
351       }
352     }
353     return pruned;
354   }
355   /**
356    * remove any hiddenColumns or selected columns and shift remaining
357    * based on a series of position, range deletions.
358    * @param deletions
359    */
360   public void pruneDeletions(ShiftList deletions) {
361     if (deletions!=null) {
362       Vector shifts=deletions.shifts;
363       if (shifts!=null && shifts.size()>0) {
364         // delete any intervals intersecting.
365         if (hiddenColumns!=null) {
366           pruneIntervalVector(shifts, hiddenColumns);
367           if (hiddenColumns!=null && hiddenColumns.size()==0) {
368             hiddenColumns=null;
369           }
370         }
371         if (selected!=null && selected.size()>0) {
372           pruneColumnList(shifts, selected);
373           if (selected!=null && selected.size()==0)
374             selected=null;
375         }
376         // and shift the rest.
377         this.compensateForEdits(deletions);
378       }
379     }
380   }
381   /**
382    * This Method is used to return all the HiddenColumn regions
383    * less than the given index.
384    * @param end int
385    * @return Vector
386    */
387   public Vector getHiddenColumns()
388   {
389     return hiddenColumns;
390   }
391   /**
392    * Return absolute column index for a visible column index
393    * @param column int column index in alignment view
394    * @return alignment column index for column
395    */
396   public int adjustForHiddenColumns(int column)
397   {
398     int result = column;
399     if (hiddenColumns != null)
400     {
401       for (int i = 0; i < hiddenColumns.size(); i++)
402       {
403         int[] region = (int[]) hiddenColumns.elementAt(i);
404         if (result >= region[0])
405         {
406           result += region[1] - region[0] + 1;
407         }
408       }
409     }
410     return result;
411   }
412
413   /**
414    * Use this method to find out where a visible column is in the alignment
415    * when hidden columns exist
416    * @param hiddenColumn int
417    * @return int
418    */
419   public int findColumnPosition(int hiddenColumn)
420   {
421     int result = hiddenColumn;
422     if (hiddenColumns != null)
423     {
424       int index = 0;
425       int gaps = 0;
426       do
427       {
428         int[] region = (int[]) hiddenColumns.elementAt(index);
429         if (hiddenColumn > region[1])
430         {
431           result -= region[1]+1-region[0];
432         }
433         index++;
434       }
435       while (index < hiddenColumns.size());
436
437       result -= gaps;
438     }
439
440     return result;
441   }
442
443   /**
444    * Use this method to determine where the next hiddenRegion starts
445    */
446   public int findHiddenRegionPosition(int hiddenRegion)
447   {
448     int result = 0;
449     if (hiddenColumns != null)
450     {
451       int index = 0;
452       int gaps = 0;
453       do
454       {
455         int[] region = (int[]) hiddenColumns.elementAt(index);
456         if(hiddenRegion==0)
457         {
458           return region[0];
459         }
460
461         gaps +=  region[1] +1 - region[0];
462         result = region[1] +1;
463         index++;
464       }
465       while(index < hiddenRegion+1);
466
467       result -= gaps;
468     }
469
470     return result;
471   }
472
473   /**
474    * THis method returns the rightmost limit of a
475    * region of an alignment with hidden columns.
476    * In otherwords, the next hidden column.
477    * @param index int
478    */
479   public int getHiddenBoundaryRight(int alPos)
480   {
481     if (hiddenColumns != null)
482     {
483       int index = 0;
484       do
485       {
486         int[] region = (int[]) hiddenColumns.elementAt(index);
487         if(alPos < region[0])
488           return region[0];
489
490         index++;
491       }
492       while(index < hiddenColumns.size());
493     }
494
495     return alPos;
496
497   }
498   /**
499    * THis method returns the rightmost limit of a
500    * region of an alignment with hidden columns.
501    * In otherwords, the next hidden column.
502    * @param index int
503    */
504   public int getHiddenBoundaryLeft(int alPos)
505   {
506     if (hiddenColumns != null)
507     {
508       int index = hiddenColumns.size()-1;
509       do
510       {
511         int[] region = (int[]) hiddenColumns.elementAt(index);
512         if(alPos > region[1])
513           return region[1];
514
515         index--;
516       }
517       while(index >-1);
518     }
519
520     return alPos;
521
522   }
523
524   public void hideSelectedColumns()
525   {
526     while (size() > 0)
527     {
528       int column = ( (Integer) getSelected().firstElement()).intValue();
529       hideColumns(column);
530     }
531
532   }
533
534   public void hideColumns(int start, int end)
535   {
536     if(hiddenColumns==null)
537       hiddenColumns = new Vector();
538
539     boolean added = false;
540     boolean overlap = false;
541
542     for (int i = 0; i < hiddenColumns.size(); i++)
543     {
544       int[] region = (int[]) hiddenColumns.elementAt(i);
545       if ( start<=region[1] && end>=region[0])
546       {
547         hiddenColumns.removeElementAt(i);
548         overlap = true;
549         break;
550       }
551       else if (end < region[0] && start < region[0])
552       {
553         hiddenColumns.insertElementAt(new int[]
554                                               {start, end}, i);
555         added = true;
556         break;
557       }
558     }
559
560     if(overlap)
561     {
562       hideColumns(start, end);
563     }
564     else if (!added)
565       hiddenColumns.addElement(new int[] {start, end});
566
567   }
568
569   /**
570    * This method will find a range of selected columns
571    * around the column specified
572    * @param res int
573    */
574   public void hideColumns(int col)
575   {
576     // First find out range of columns to hide
577     int min = col, max = col+1;
578     while( contains(min) )
579     {  removeElement(min); min --;  }
580
581     while( contains(max) )
582     { removeElement(max);  max ++;  }
583
584     min++; max--;
585
586     hideColumns(min, max);
587   }
588
589   public void revealAllHiddenColumns()
590   {
591     if(hiddenColumns!=null)
592     {
593       for (int i = 0; i < hiddenColumns.size(); i++)
594       {
595         int[] region = (int[]) hiddenColumns.elementAt(i);
596         for (int j = region[0]; j < region[1]+1; j++)
597         {
598           addElement(j);
599         }
600       }
601     }
602
603     hiddenColumns = null;
604   }
605
606   public void revealHiddenColumns(int res)
607   {
608     for(int i=0; i<hiddenColumns.size(); i++)
609     {
610       int [] region = (int[])hiddenColumns.elementAt(i);
611       if( res == region[0])
612       {
613         for (int j = region[0]; j < region[1]+1; j++)
614         {
615           addElement(j);
616         }
617
618         hiddenColumns.removeElement(region);
619         break;
620       }
621     }
622     if(hiddenColumns.size()==0)
623       hiddenColumns = null;
624   }
625
626   public boolean isVisible(int column)
627   {
628     for(int i=0; i<hiddenColumns.size(); i++)
629     {
630       int [] region = (int[])hiddenColumns.elementAt(i);
631       if( column >= region[0] && column <= region[1])
632       {
633         return false;
634       }
635     }
636     return true;
637   }
638   /**
639    * Copy constructor
640    * @param copy
641    */
642   public ColumnSelection(ColumnSelection copy) {
643     if (copy!=null) {
644       if (copy.selected!=null) {
645         selected = new Vector();
646         for (int i=0,j=copy.selected.size(); i<j; i++) {
647           selected.setElementAt( ((Integer) copy.selected.elementAt(i)), i);
648         }
649       }
650       if (copy.hiddenColumns!=null) {
651         hiddenColumns=new Vector();
652         for (int i=0,j=copy.hiddenColumns.size(); i<j; i++) {
653           int[] rh,cp;
654           rh = (int[])copy.hiddenColumns.elementAt(i);
655           if (rh!=null) {
656             cp = new int[rh.length];
657             System.arraycopy(rh, 0, cp, 0, rh.length);
658             hiddenColumns.setElementAt(cp, i);
659           }
660         }
661       }
662     }
663   }
664   /**
665    * ColumnSelection
666    */
667   public ColumnSelection()
668   {
669   }
670
671   public String[] getVisibleSequenceStrings(int start, int end, SequenceI[] seqs) {
672     int i,iSize=seqs.length;
673     String selection[] = new String[iSize];
674     if (hiddenColumns!=null && hiddenColumns.size()>0)
675     {
676       for (i=0; i<iSize; i++) {
677         StringBuffer visibleSeq = new StringBuffer();
678         Vector regions = getHiddenColumns();
679
680         int blockStart = start, blockEnd=end;
681         int [] region;
682         int hideStart, hideEnd;
683
684         for (int j = 0; j < regions.size(); j++)
685         {
686           region = (int[]) regions.elementAt(j);
687           hideStart = region[0];
688           hideEnd = region[1];
689
690           if(hideStart < start)
691           {
692             continue;
693           }
694
695           blockStart = Math.min(blockStart, hideEnd+1);
696           blockEnd = Math.min(blockEnd, hideStart);
697
698           if(blockStart>blockEnd)
699           {
700             break;
701           }
702
703
704           visibleSeq.append(seqs[i].getSequence(blockStart, blockEnd));
705
706           blockStart = hideEnd+1;
707           blockEnd = end;
708         }
709
710         if(end>blockStart)
711           visibleSeq.append(seqs[i].getSequence(blockStart, end));
712
713         selection[i] = visibleSeq.toString();
714       }
715     }
716     else
717     {
718       for(i=0; i<iSize; i++)
719       {
720         selection[i] = seqs[i].getSequence(start, end);
721       }
722     }
723
724     return selection;
725   }
726   /**
727    * return all visible segments between the given start and end boundaries
728    * 
729    * @param start (first column inclusive from 0)
730    * @param end (last column - not inclusive)
731    * @return int[] {i_start, i_end, ..} where intervals lie in start<=i_start<=i_end<end 
732    */
733   public int[] getVisibleContigs(int start, int end) {
734     if (hiddenColumns!=null && hiddenColumns.size()>0)
735     {
736       Vector visiblecontigs=new Vector();
737       Vector regions = getHiddenColumns();
738
739       int vstart = start;
740       int [] region;
741       int hideStart, hideEnd;
742
743       for (int j = 0; vstart<end && j < regions.size(); j++)
744       {
745         region = (int[]) regions.elementAt(j);
746         hideStart = region[0];
747         hideEnd = region[1];
748
749         if(hideEnd < vstart)
750         {
751           continue;
752         }
753         if (hideStart>vstart) {
754           visiblecontigs.addElement(new int[] {vstart, hideStart-1});
755         }
756         vstart=hideEnd+1;
757       }
758
759       if(vstart<end)
760         visiblecontigs.addElement(new int[] { vstart, end-1});
761       int[] vcontigs = new int[visiblecontigs.size()*2];
762       for (int i=0,j=visiblecontigs.size(); i<j; i++) {
763         int [] vc = (int[]) visiblecontigs.get(i);
764         visiblecontigs.setElementAt(null, i);
765         vcontigs[i*2] = vc[0];
766         vcontigs[i*2+1] = vc[1];
767       }
768       visiblecontigs.clear();
769       return vcontigs;
770     }     
771     else
772     {
773       return new int[] { start, end-1 };
774     }
775   }
776 }