hidden region recovery in JPred and new hidden region pruning function
[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.get(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.get(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.set(i, ((Integer) copy.selected.get(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.get(i);
655             if (rh!=null) {
656               cp = new int[rh.length];
657               System.arraycopy(rh, 0, cp, 0, rh.length);
658               hiddenColumns.set(i, cp);
659             }
660           }
661         }
662       }
663     }
664
665   /**
666    * ColumnSelection
667    */
668   public ColumnSelection()
669   {
670   }
671 }