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