Fix load 1 col hidden bug
[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     if(min>max)\r
604       min = max;\r
605 \r
606     hideColumns(min, max);\r
607   }\r
608 \r
609   public void revealAllHiddenColumns()\r
610   {\r
611     if(hiddenColumns!=null)\r
612     {\r
613       for (int i = 0; i < hiddenColumns.size(); i++)\r
614       {\r
615         int[] region = (int[]) hiddenColumns.elementAt(i);\r
616         for (int j = region[0]; j < region[1]+1; j++)\r
617         {\r
618           addElement(j);\r
619         }\r
620       }\r
621     }\r
622 \r
623     hiddenColumns = null;\r
624   }\r
625 \r
626   public void revealHiddenColumns(int res)\r
627   {\r
628     for(int i=0; i<hiddenColumns.size(); i++)\r
629     {\r
630       int [] region = (int[])hiddenColumns.elementAt(i);\r
631       if( res == region[0])\r
632       {\r
633         for (int j = region[0]; j < region[1]+1; j++)\r
634         {\r
635           addElement(j);\r
636         }\r
637 \r
638         hiddenColumns.removeElement(region);\r
639         break;\r
640       }\r
641     }\r
642     if(hiddenColumns.size()==0)\r
643       hiddenColumns = null;\r
644   }\r
645 \r
646   public boolean isVisible(int column)\r
647   {\r
648     for(int i=0; i<hiddenColumns.size(); i++)\r
649     {\r
650       int [] region = (int[])hiddenColumns.elementAt(i);\r
651       if( column >= region[0] && column <= region[1])\r
652       {\r
653         return false;\r
654       }\r
655     }\r
656     return true;\r
657   }\r
658   /**\r
659    * Copy constructor\r
660    * @param copy\r
661    */\r
662   public ColumnSelection(ColumnSelection copy) {\r
663     if (copy!=null) {\r
664       if (copy.selected!=null) {\r
665         selected = new Vector();\r
666         for (int i=0,j=copy.selected.size(); i<j; i++) {\r
667           selected.addElement(copy.selected.elementAt(i));\r
668         }\r
669       }\r
670       if (copy.hiddenColumns!=null) {\r
671         hiddenColumns=new Vector(copy.hiddenColumns.size());\r
672         for (int i=0,j=copy.hiddenColumns.size(); i<j; i++) {\r
673           int[] rh,cp;\r
674           rh = (int[])copy.hiddenColumns.elementAt(i);\r
675           if (rh!=null) {\r
676             cp = new int[rh.length];\r
677             System.arraycopy(rh, 0, cp, 0, rh.length);\r
678             hiddenColumns.addElement(cp);\r
679           }\r
680         }\r
681       }\r
682     }\r
683   }\r
684   /**\r
685    * ColumnSelection\r
686    */\r
687   public ColumnSelection()\r
688   {\r
689   }\r
690 \r
691   public String[] getVisibleSequenceStrings(int start, int end, SequenceI[] seqs) {\r
692     int i,iSize=seqs.length;\r
693     String selection[] = new String[iSize];\r
694     if (hiddenColumns!=null && hiddenColumns.size()>0)\r
695     {\r
696       for (i=0; i<iSize; i++) {\r
697         StringBuffer visibleSeq = new StringBuffer();\r
698         Vector regions = getHiddenColumns();\r
699 \r
700         int blockStart = start, blockEnd=end;\r
701         int [] region;\r
702         int hideStart, hideEnd;\r
703 \r
704         for (int j = 0; j < regions.size(); j++)\r
705         {\r
706           region = (int[]) regions.elementAt(j);\r
707           hideStart = region[0];\r
708           hideEnd = region[1];\r
709 \r
710           if(hideStart < start)\r
711           {\r
712             continue;\r
713           }\r
714 \r
715           blockStart = Math.min(blockStart, hideEnd+1);\r
716           blockEnd = Math.min(blockEnd, hideStart);\r
717 \r
718           if(blockStart>blockEnd)\r
719           {\r
720             break;\r
721           }\r
722 \r
723 \r
724           visibleSeq.append(seqs[i].getSequence(blockStart, blockEnd));\r
725 \r
726           blockStart = hideEnd+1;\r
727           blockEnd = end;\r
728         }\r
729 \r
730         if(end>blockStart)\r
731           visibleSeq.append(seqs[i].getSequence(blockStart, end));\r
732 \r
733         selection[i] = visibleSeq.toString();\r
734       }\r
735     }\r
736     else\r
737     {\r
738       for(i=0; i<iSize; i++)\r
739       {\r
740         selection[i] = seqs[i].getSequence(start, end);\r
741       }\r
742     }\r
743 \r
744     return selection;\r
745   }\r
746   /**\r
747    * return all visible segments between the given start and end boundaries\r
748    *\r
749    * @param start (first column inclusive from 0)\r
750    * @param end (last column - not inclusive)\r
751    * @return int[] {i_start, i_end, ..} where intervals lie in start<=i_start<=i_end<end\r
752    */\r
753   public int[] getVisibleContigs(int start, int end) {\r
754     if (hiddenColumns!=null && hiddenColumns.size()>0)\r
755     {\r
756       Vector visiblecontigs=new Vector();\r
757       Vector regions = getHiddenColumns();\r
758 \r
759       int vstart = start;\r
760       int [] region;\r
761       int hideStart, hideEnd;\r
762 \r
763       for (int j = 0; vstart<end && j < regions.size(); j++)\r
764       {\r
765         region = (int[]) regions.elementAt(j);\r
766         hideStart = region[0];\r
767         hideEnd = region[1];\r
768 \r
769         if(hideEnd < vstart)\r
770         {\r
771           continue;\r
772         }\r
773         if (hideStart>vstart) {\r
774           visiblecontigs.addElement(new int[] {vstart, hideStart-1});\r
775         }\r
776         vstart=hideEnd+1;\r
777       }\r
778 \r
779       if(vstart<end)\r
780         visiblecontigs.addElement(new int[] { vstart, end-1});\r
781       int[] vcontigs = new int[visiblecontigs.size()*2];\r
782       for (int i=0,j=visiblecontigs.size(); i<j; i++) {\r
783         int [] vc = (int[]) visiblecontigs.elementAt(i);\r
784         visiblecontigs.setElementAt(null, i);\r
785         vcontigs[i*2] = vc[0];\r
786         vcontigs[i*2+1] = vc[1];\r
787       }\r
788       visiblecontigs.removeAllElements();\r
789       return vcontigs;\r
790     }\r
791     else\r
792     {\r
793       return new int[] { start, end-1 };\r
794     }\r
795   }\r
796 }\r