JAL-2388 Working hidden regions hide/show in desktop
[jalview.git] / src / jalview / datamodel / HiddenColumns.java
1 package jalview.datamodel;
2
3 import jalview.util.Comparison;
4
5 import java.util.ArrayList;
6 import java.util.Collections;
7 import java.util.List;
8 import java.util.Vector;
9
10 public class HiddenColumns
11 {
12   /*
13    * list of hidden column [start, end] ranges; the list is maintained in
14    * ascending start column order
15    */
16   private Vector<int[]> hiddenColumns;
17
18   /**
19    * This Method is used to return all the HiddenColumn regions
20    * 
21    * @return empty list or List of hidden column intervals
22    */
23   public List<int[]> getHiddenRegions()
24   {
25     return hiddenColumns == null ? Collections.<int[]> emptyList()
26             : hiddenColumns;
27   }
28
29   /**
30    * Find the number of hidden columns
31    * 
32    * @return number of hidden columns
33    */
34   public int getSize()
35   {
36     int size = 0;
37     if (hasHidden())
38     {
39       for (int[] range : hiddenColumns)
40       {
41         size += range[1] - range[0] + 1;
42       }
43     }
44     return size;
45   }
46
47   /**
48    * Answers if there are any hidden columns
49    * 
50    * @return true if there are hidden columns
51    */
52   public boolean hasHidden()
53   {
54     return (hiddenColumns != null) && (!hiddenColumns.isEmpty());
55   }
56
57   @Override
58   public boolean equals(Object obj)
59   {
60     if (!(obj instanceof HiddenColumns))
61     {
62       return false;
63     }
64     HiddenColumns that = (HiddenColumns) obj;
65
66     /*
67      * check hidden columns are either both null, or match
68      */
69     if (this.hiddenColumns == null)
70     {
71       return (that.hiddenColumns == null);
72     }
73     if (that.hiddenColumns == null
74             || that.hiddenColumns.size() != this.hiddenColumns.size())
75     {
76       return false;
77     }
78     int i = 0;
79     for (int[] thisRange : hiddenColumns)
80     {
81       int[] thatRange = that.hiddenColumns.get(i++);
82       if (thisRange[0] != thatRange[0] || thisRange[1] != thatRange[1])
83       {
84         return false;
85       }
86     }
87     return true;
88   }
89
90   /**
91    * Return absolute column index for a visible column index
92    * 
93    * @param column
94    *          int column index in alignment view (count from zero)
95    * @return alignment column index for column
96    */
97   public int adjustForHiddenColumns(int column)
98   {
99     int result = column;
100     if (hiddenColumns != null)
101     {
102       for (int i = 0; i < hiddenColumns.size(); i++)
103       {
104         int[] region = hiddenColumns.elementAt(i);
105         if (result >= region[0])
106         {
107           result += region[1] - region[0] + 1;
108         }
109       }
110     }
111     return result;
112   }
113
114   /**
115    * Use this method to find out where a column will appear in the visible
116    * alignment when hidden columns exist. If the column is not visible, then the
117    * left-most visible column will always be returned.
118    * 
119    * @param hiddenColumn
120    *          the column index in the full alignment including hidden columns
121    * @return the position of the column in the visible alignment
122    */
123   public int findColumnPosition(int hiddenColumn)
124   {
125     int result = hiddenColumn;
126     if (hiddenColumns != null)
127     {
128       int index = 0;
129       int[] region;
130       do
131       {
132         region = hiddenColumns.elementAt(index++);
133         if (hiddenColumn > region[1])
134         {
135           result -= region[1] + 1 - region[0];
136         }
137       } while ((hiddenColumn > region[1]) && (index < hiddenColumns.size()));
138
139       if (hiddenColumn >= region[0] && hiddenColumn <= region[1])
140       {
141         // Here the hidden column is within a region, so
142         // we want to return the position of region[0]-1, adjusted for any
143         // earlier hidden columns.
144         // Calculate the difference between the actual hidden col position
145         // and region[0]-1, and then subtract from result to convert result from
146         // the adjusted hiddenColumn value to the adjusted region[0]-1 value
147
148         // However, if the region begins at 0 we cannot return region[0]-1
149         // just return 0
150         if (region[0] == 0)
151         {
152           return 0;
153         }
154         else
155         {
156           return result - (hiddenColumn - region[0] + 1);
157         }
158       }
159     }
160     return result; // return the shifted position after removing hidden columns.
161   }
162
163   /**
164    * Find the visible column which is a given visible number of columns to the
165    * left of another visible column. i.e. for a startColumn x, the column which
166    * is distance 1 away will be column x-1.
167    * 
168    * @param visibleDistance
169    *          the number of visible columns to offset by
170    * @param startColumn
171    *          the column to start from
172    * @return the position of the column in the visible alignment
173    */
174   public int subtractVisibleColumns(int visibleDistance, int startColumn)
175   {
176     int distance = visibleDistance;
177
178     // in case startColumn is in a hidden region, move it to the left
179     int start = adjustForHiddenColumns(findColumnPosition(startColumn));
180
181     // get index of hidden region to left of start
182     int index = getHiddenIndexLeft(start);
183     if (index == -1)
184     {
185       // no hidden regions to left of startColumn
186       return start - distance;
187     }
188
189     // walk backwards through the alignment subtracting the counts of visible
190     // columns from distance
191     int[] region;
192     int gap = 0;
193     int nextstart = start;
194
195     while ((index > -1) && (distance - gap > 0))
196     {
197       // subtract the gap to right of region from distance
198       distance -= gap;
199       start = nextstart;
200
201       // calculate the next gap
202       region = hiddenColumns.get(index);
203       gap = start - region[1];
204
205       // set start to just to left of current region
206       nextstart = region[0] - 1;
207       index--;
208     }
209
210     if (distance - gap > 0)
211     {
212       // fell out of loop because there are no more hidden regions
213       distance -= gap;
214       return nextstart - distance;
215     }
216     return start - distance;
217
218   }
219
220   /**
221    * Use this method to determine where the next hiddenRegion starts
222    * 
223    * @param hiddenRegion
224    *          index of hidden region (counts from 0)
225    * @return column number in visible view
226    */
227   public int findHiddenRegionPosition(int hiddenRegion)
228   {
229     int result = 0;
230     if (hiddenColumns != null)
231     {
232       int index = 0;
233       int gaps = 0;
234       do
235       {
236         int[] region = hiddenColumns.elementAt(index);
237         if (hiddenRegion == 0)
238         {
239           return region[0];
240         }
241
242         gaps += region[1] + 1 - region[0];
243         result = region[1] + 1;
244         index++;
245       } while (index <= hiddenRegion);
246
247       result -= gaps;
248     }
249
250     return result;
251   }
252
253   /**
254    * This method returns the rightmost limit of a region of an alignment with
255    * hidden columns. In otherwords, the next hidden column.
256    * 
257    * @param index
258    *          int
259    */
260   public int getHiddenBoundaryRight(int alPos)
261   {
262     if (hiddenColumns != null)
263     {
264       int index = 0;
265       do
266       {
267         int[] region = hiddenColumns.elementAt(index);
268         if (alPos < region[0])
269         {
270           return region[0];
271         }
272
273         index++;
274       } while (index < hiddenColumns.size());
275     }
276
277     return alPos;
278
279   }
280
281   /**
282    * This method returns the leftmost limit of a region of an alignment with
283    * hidden columns. In otherwords, the previous hidden column.
284    * 
285    * @param index
286    *          int
287    */
288   public int getHiddenBoundaryLeft(int alPos)
289   {
290     if (hiddenColumns != null)
291     {
292       int index = hiddenColumns.size() - 1;
293       do
294       {
295         int[] region = hiddenColumns.elementAt(index);
296         if (alPos > region[1])
297         {
298           return region[1];
299         }
300
301         index--;
302       } while (index > -1);
303     }
304
305     return alPos;
306
307   }
308
309   /**
310    * This method returns the index of the hidden region to the left of a column
311    * position. If the column is in a hidden region it returns the index of the
312    * region to the left. If there is no hidden region to the left it returns -1.
313    * 
314    * @param pos
315    *          int
316    */
317   private int getHiddenIndexLeft(int pos)
318   {
319     if (hiddenColumns != null)
320     {
321       int index = hiddenColumns.size() - 1;
322       do
323       {
324         int[] region = hiddenColumns.elementAt(index);
325         if (pos > region[1])
326         {
327           return index;
328         }
329
330         index--;
331       } while (index > -1);
332     }
333
334     return -1;
335
336   }
337
338   /**
339    * Adds the specified column range to the hidden columns
340    * 
341    * @param start
342    * @param end
343    */
344   public void hideColumns(int start, int end)
345   {
346     if (hiddenColumns == null)
347     {
348       hiddenColumns = new Vector<int[]>();
349     }
350
351     /*
352      * traverse existing hidden ranges and insert / amend / append as
353      * appropriate
354      */
355     for (int i = 0; i < hiddenColumns.size(); i++)
356     {
357       int[] region = hiddenColumns.elementAt(i);
358
359       if (end < region[0] - 1)
360       {
361         /*
362          * insert discontiguous preceding range
363          */
364         hiddenColumns.insertElementAt(new int[] { start, end }, i);
365         return;
366       }
367
368       if (end <= region[1])
369       {
370         /*
371          * new range overlaps existing, or is contiguous preceding it - adjust
372          * start column
373          */
374         region[0] = Math.min(region[0], start);
375         return;
376       }
377
378       if (start <= region[1] + 1)
379       {
380         /*
381          * new range overlaps existing, or is contiguous following it - adjust
382          * start and end columns
383          */
384         region[0] = Math.min(region[0], start);
385         region[1] = Math.max(region[1], end);
386
387         /*
388          * also update or remove any subsequent ranges 
389          * that are overlapped
390          */
391         while (i < hiddenColumns.size() - 1)
392         {
393           int[] nextRegion = hiddenColumns.get(i + 1);
394           if (nextRegion[0] > end + 1)
395           {
396             /*
397              * gap to next hidden range - no more to update
398              */
399             break;
400           }
401           region[1] = Math.max(nextRegion[1], end);
402           hiddenColumns.remove(i + 1);
403         }
404         return;
405       }
406     }
407
408     /*
409      * remaining case is that the new range follows everything else
410      */
411     hiddenColumns.addElement(new int[] { start, end });
412   }
413
414   public boolean isVisible(int column)
415   {
416     if (hiddenColumns != null)
417     {
418       for (int[] region : hiddenColumns)
419       {
420         if (column >= region[0] && column <= region[1])
421         {
422           return false;
423         }
424       }
425     }
426
427     return true;
428   }
429
430   /**
431    * ColumnSelection
432    */
433   public HiddenColumns()
434   {
435   }
436
437   /**
438    * Copy constructor
439    * 
440    * @param copy
441    */
442   public HiddenColumns(HiddenColumns copy)
443   {
444     if (copy != null)
445     {
446       if (copy.hiddenColumns != null)
447       {
448         hiddenColumns = new Vector<int[]>(copy.hiddenColumns.size());
449         for (int i = 0, j = copy.hiddenColumns.size(); i < j; i++)
450         {
451           int[] rh, cp;
452           rh = copy.hiddenColumns.elementAt(i);
453           if (rh != null)
454           {
455             cp = new int[rh.length];
456             System.arraycopy(rh, 0, cp, 0, rh.length);
457             hiddenColumns.addElement(cp);
458           }
459         }
460       }
461     }
462   }
463
464   /**
465    * propagate shift in alignment columns to column selection
466    * 
467    * @param start
468    *          beginning of edit
469    * @param left
470    *          shift in edit (+ve for removal, or -ve for inserts)
471    */
472   public List<int[]> compensateForEdit(int start, int change,
473           ColumnSelection sel)
474   {
475     List<int[]> deletedHiddenColumns = null;
476
477     if (hiddenColumns != null)
478     {
479       deletedHiddenColumns = new ArrayList<int[]>();
480       int hSize = hiddenColumns.size();
481       for (int i = 0; i < hSize; i++)
482       {
483         int[] region = hiddenColumns.elementAt(i);
484         if (region[0] > start && start + change > region[1])
485         {
486           deletedHiddenColumns.add(region);
487
488           hiddenColumns.removeElementAt(i);
489           i--;
490           hSize--;
491           continue;
492         }
493
494         if (region[0] > start)
495         {
496           region[0] -= change;
497           region[1] -= change;
498         }
499
500         if (region[0] < 0)
501         {
502           region[0] = 0;
503         }
504
505       }
506
507       this.revealHiddenColumns(0, sel);
508     }
509
510     return deletedHiddenColumns;
511   }
512
513   /**
514    * propagate shift in alignment columns to column selection special version of
515    * compensateForEdit - allowing for edits within hidden regions
516    * 
517    * @param start
518    *          beginning of edit
519    * @param left
520    *          shift in edit (+ve for removal, or -ve for inserts)
521    */
522   public void compensateForDelEdits(int start, int change)
523   {
524     if (hiddenColumns != null)
525     {
526       for (int i = 0; i < hiddenColumns.size(); i++)
527       {
528         int[] region = hiddenColumns.elementAt(i);
529         if (region[0] >= start)
530         {
531           region[0] -= change;
532         }
533         if (region[1] >= start)
534         {
535           region[1] -= change;
536         }
537         if (region[1] < region[0])
538         {
539           hiddenColumns.removeElementAt(i--);
540         }
541
542         if (region[0] < 0)
543         {
544           region[0] = 0;
545         }
546         if (region[1] < 0)
547         {
548           region[1] = 0;
549         }
550       }
551     }
552   }
553
554   /**
555    * return all visible segments between the given start and end boundaries
556    * 
557    * @param start
558    *          (first column inclusive from 0)
559    * @param end
560    *          (last column - not inclusive)
561    * @return int[] {i_start, i_end, ..} where intervals lie in
562    *         start<=i_start<=i_end<end
563    */
564   public int[] getVisibleContigs(int start, int end)
565   {
566     if (hiddenColumns != null && hiddenColumns.size() > 0)
567     {
568       List<int[]> visiblecontigs = new ArrayList<int[]>();
569       List<int[]> regions = getHiddenRegions();
570
571       int vstart = start;
572       int[] region;
573       int hideStart, hideEnd;
574
575       for (int j = 0; vstart < end && j < regions.size(); j++)
576       {
577         region = regions.get(j);
578         hideStart = region[0];
579         hideEnd = region[1];
580
581         if (hideEnd < vstart)
582         {
583           continue;
584         }
585         if (hideStart > vstart)
586         {
587           visiblecontigs.add(new int[] { vstart, hideStart - 1 });
588         }
589         vstart = hideEnd + 1;
590       }
591
592       if (vstart < end)
593       {
594         visiblecontigs.add(new int[] { vstart, end - 1 });
595       }
596       int[] vcontigs = new int[visiblecontigs.size() * 2];
597       for (int i = 0, j = visiblecontigs.size(); i < j; i++)
598       {
599         int[] vc = visiblecontigs.get(i);
600         visiblecontigs.set(i, null);
601         vcontigs[i * 2] = vc[0];
602         vcontigs[i * 2 + 1] = vc[1];
603       }
604       visiblecontigs.clear();
605       return vcontigs;
606     }
607     else
608     {
609       return new int[] { start, end - 1 };
610     }
611   }
612
613   public String[] getVisibleSequenceStrings(int start, int end,
614           SequenceI[] seqs)
615   {
616     int i, iSize = seqs.length;
617     String selections[] = new String[iSize];
618     if (hiddenColumns != null && hiddenColumns.size() > 0)
619     {
620       for (i = 0; i < iSize; i++)
621       {
622         StringBuffer visibleSeq = new StringBuffer();
623         List<int[]> regions = getHiddenRegions();
624
625         int blockStart = start, blockEnd = end;
626         int[] region;
627         int hideStart, hideEnd;
628
629         for (int j = 0; j < regions.size(); j++)
630         {
631           region = regions.get(j);
632           hideStart = region[0];
633           hideEnd = region[1];
634
635           if (hideStart < start)
636           {
637             continue;
638           }
639
640           blockStart = Math.min(blockStart, hideEnd + 1);
641           blockEnd = Math.min(blockEnd, hideStart);
642
643           if (blockStart > blockEnd)
644           {
645             break;
646           }
647
648           visibleSeq.append(seqs[i].getSequence(blockStart, blockEnd));
649
650           blockStart = hideEnd + 1;
651           blockEnd = end;
652         }
653
654         if (end > blockStart)
655         {
656           visibleSeq.append(seqs[i].getSequence(blockStart, end));
657         }
658
659         selections[i] = visibleSeq.toString();
660       }
661     }
662     else
663     {
664       for (i = 0; i < iSize; i++)
665       {
666         selections[i] = seqs[i].getSequenceAsString(start, end);
667       }
668     }
669
670     return selections;
671   }
672
673   /**
674    * Locate the first and last position visible for this sequence. if seq isn't
675    * visible then return the position of the left and right of the hidden
676    * boundary region, and the corresponding alignment column indices for the
677    * extent of the sequence
678    * 
679    * @param seq
680    * @return int[] { visible start, visible end, first seqpos, last seqpos,
681    *         alignment index for seq start, alignment index for seq end }
682    */
683   public int[] locateVisibleBoundsOfSequence(SequenceI seq)
684   {
685     int fpos = seq.getStart(), lpos = seq.getEnd();
686     int start = 0;
687
688     if (hiddenColumns == null || hiddenColumns.size() == 0)
689     {
690       int ifpos = seq.findIndex(fpos) - 1, ilpos = seq.findIndex(lpos) - 1;
691       return new int[] { ifpos, ilpos, fpos, lpos, ifpos, ilpos };
692     }
693
694     // Simply walk along the sequence whilst watching for hidden column
695     // boundaries
696     List<int[]> regions = getHiddenRegions();
697     int spos = fpos, lastvispos = -1, rcount = 0, hideStart = seq
698             .getLength(), hideEnd = -1;
699     int visPrev = 0, visNext = 0, firstP = -1, lastP = -1;
700     boolean foundStart = false;
701     for (int p = 0, pLen = seq.getLength(); spos <= seq.getEnd()
702             && p < pLen; p++)
703     {
704       if (!Comparison.isGap(seq.getCharAt(p)))
705       {
706         // keep track of first/last column
707         // containing sequence data regardless of visibility
708         if (firstP == -1)
709         {
710           firstP = p;
711         }
712         lastP = p;
713         // update hidden region start/end
714         while (hideEnd < p && rcount < regions.size())
715         {
716           int[] region = regions.get(rcount++);
717           visPrev = visNext;
718           visNext += region[0] - visPrev;
719           hideStart = region[0];
720           hideEnd = region[1];
721         }
722         if (hideEnd < p)
723         {
724           hideStart = seq.getLength();
725         }
726         // update visible boundary for sequence
727         if (p < hideStart)
728         {
729           if (!foundStart)
730           {
731             fpos = spos;
732             start = p;
733             foundStart = true;
734           }
735           lastvispos = p;
736           lpos = spos;
737         }
738         // look for next sequence position
739         spos++;
740       }
741     }
742     if (foundStart)
743     {
744       return new int[] { findColumnPosition(start),
745           findColumnPosition(lastvispos), fpos, lpos, firstP, lastP };
746     }
747     // otherwise, sequence was completely hidden
748     return new int[] { visPrev, visNext, 0, 0, firstP, lastP };
749   }
750
751   /**
752    * delete any columns in alignmentAnnotation that are hidden (including
753    * sequence associated annotation).
754    * 
755    * @param alignmentAnnotation
756    */
757   public void makeVisibleAnnotation(AlignmentAnnotation alignmentAnnotation)
758   {
759     makeVisibleAnnotation(-1, -1, alignmentAnnotation);
760   }
761
762   /**
763    * delete any columns in alignmentAnnotation that are hidden (including
764    * sequence associated annotation).
765    * 
766    * @param start
767    *          remove any annotation to the right of this column
768    * @param end
769    *          remove any annotation to the left of this column
770    * @param alignmentAnnotation
771    *          the annotation to operate on
772    */
773   public void makeVisibleAnnotation(int start, int end,
774           AlignmentAnnotation alignmentAnnotation)
775   {
776     if (alignmentAnnotation.annotations == null)
777     {
778       return;
779     }
780     if (start == end && end == -1)
781     {
782       start = 0;
783       end = alignmentAnnotation.annotations.length;
784     }
785     if (hiddenColumns != null && hiddenColumns.size() > 0)
786     {
787       // then mangle the alignmentAnnotation annotation array
788       Vector<Annotation[]> annels = new Vector<Annotation[]>();
789       Annotation[] els = null;
790       List<int[]> regions = getHiddenRegions();
791       int blockStart = start, blockEnd = end;
792       int[] region;
793       int hideStart, hideEnd, w = 0;
794
795       for (int j = 0; j < regions.size(); j++)
796       {
797         region = regions.get(j);
798         hideStart = region[0];
799         hideEnd = region[1];
800
801         if (hideStart < start)
802         {
803           continue;
804         }
805
806         blockStart = Math.min(blockStart, hideEnd + 1);
807         blockEnd = Math.min(blockEnd, hideStart);
808
809         if (blockStart > blockEnd)
810         {
811           break;
812         }
813
814         annels.addElement(els = new Annotation[blockEnd - blockStart]);
815         System.arraycopy(alignmentAnnotation.annotations, blockStart, els,
816                 0, els.length);
817         w += els.length;
818         blockStart = hideEnd + 1;
819         blockEnd = end;
820       }
821
822       if (end > blockStart)
823       {
824         annels.addElement(els = new Annotation[end - blockStart + 1]);
825         if ((els.length + blockStart) <= alignmentAnnotation.annotations.length)
826         {
827           // copy just the visible segment of the annotation row
828           System.arraycopy(alignmentAnnotation.annotations, blockStart,
829                   els, 0, els.length);
830         }
831         else
832         {
833           // copy to the end of the annotation row
834           System.arraycopy(alignmentAnnotation.annotations, blockStart,
835                   els, 0,
836                   (alignmentAnnotation.annotations.length - blockStart));
837         }
838         w += els.length;
839       }
840       if (w == 0)
841       {
842         return;
843       }
844
845       alignmentAnnotation.annotations = new Annotation[w];
846       w = 0;
847
848       for (Annotation[] chnk : annels)
849       {
850         System.arraycopy(chnk, 0, alignmentAnnotation.annotations, w,
851                 chnk.length);
852         w += chnk.length;
853       }
854     }
855     else
856     {
857       alignmentAnnotation.restrict(start, end);
858     }
859   }
860
861   /**
862    * 
863    * @return true if there are columns hidden
864    */
865   public boolean hasHiddenColumns()
866   {
867     return hiddenColumns != null && hiddenColumns.size() > 0;
868   }
869
870   /**
871    * 
872    * @return true if there are more than one set of columns hidden
873    */
874   public boolean hasManyHiddenColumns()
875   {
876     return hiddenColumns != null && hiddenColumns.size() > 1;
877   }
878
879   /**
880    * mark the columns corresponding to gap characters as hidden in the column
881    * selection
882    * 
883    * @param sr
884    */
885   public void hideInsertionsFor(SequenceI sr)
886   {
887     List<int[]> inserts = sr.getInsertions();
888     for (int[] r : inserts)
889     {
890       hideColumns(r[0], r[1]);
891     }
892   }
893
894   /**
895    * Unhides, and adds to the selection list, all hidden columns
896    */
897   public void revealAllHiddenColumns(ColumnSelection sel)
898   {
899     if (hiddenColumns != null)
900     {
901       for (int i = 0; i < hiddenColumns.size(); i++)
902       {
903         int[] region = hiddenColumns.elementAt(i);
904         for (int j = region[0]; j < region[1] + 1; j++)
905         {
906           sel.addElement(j);
907         }
908       }
909     }
910
911     hiddenColumns = null;
912   }
913
914   /**
915    * Reveals, and marks as selected, the hidden column range with the given
916    * start column
917    * 
918    * @param start
919    */
920   public void revealHiddenColumns(int start, ColumnSelection sel)
921   {
922     for (int i = 0; i < hiddenColumns.size(); i++)
923     {
924       int[] region = hiddenColumns.elementAt(i);
925       if (start == region[0])
926       {
927         for (int j = region[0]; j < region[1] + 1; j++)
928         {
929           sel.addElement(j);
930         }
931
932         hiddenColumns.removeElement(region);
933         break;
934       }
935     }
936     if (hiddenColumns.size() == 0)
937     {
938       hiddenColumns = null;
939     }
940   }
941
942   /**
943    * removes intersection of position,length ranges in deletions from the
944    * start,end regions marked in intervals.
945    * 
946    * @param shifts
947    * @param intervals
948    * @return
949    */
950   private boolean pruneIntervalVector(final List<int[]> shifts,
951           Vector<int[]> intervals)
952   {
953     boolean pruned = false;
954     int i = 0, j = intervals.size() - 1, s = 0, t = shifts.size() - 1;
955     int hr[] = intervals.elementAt(i);
956     int sr[] = shifts.get(s);
957     while (i <= j && s <= t)
958     {
959       boolean trailinghn = hr[1] >= sr[0];
960       if (!trailinghn)
961       {
962         if (i < j)
963         {
964           hr = intervals.elementAt(++i);
965         }
966         else
967         {
968           i++;
969         }
970         continue;
971       }
972       int endshift = sr[0] + sr[1]; // deletion ranges - -ve means an insert
973       if (endshift < hr[0] || endshift < sr[0])
974       { // leadinghc disjoint or not a deletion
975         if (s < t)
976         {
977           sr = shifts.get(++s);
978         }
979         else
980         {
981           s++;
982         }
983         continue;
984       }
985       boolean leadinghn = hr[0] >= sr[0];
986       boolean leadinghc = hr[0] < endshift;
987       boolean trailinghc = hr[1] < endshift;
988       if (leadinghn)
989       {
990         if (trailinghc)
991         { // deleted hidden region.
992           intervals.removeElementAt(i);
993           pruned = true;
994           j--;
995           if (i <= j)
996           {
997             hr = intervals.elementAt(i);
998           }
999           continue;
1000         }
1001         if (leadinghc)
1002         {
1003           hr[0] = endshift; // clip c terminal region
1004           leadinghn = !leadinghn;
1005           pruned = true;
1006         }
1007       }
1008       if (!leadinghn)
1009       {
1010         if (trailinghc)
1011         {
1012           if (trailinghn)
1013           {
1014             hr[1] = sr[0] - 1;
1015             pruned = true;
1016           }
1017         }
1018         else
1019         {
1020           // sr contained in hr
1021           if (s < t)
1022           {
1023             sr = shifts.get(++s);
1024           }
1025           else
1026           {
1027             s++;
1028           }
1029           continue;
1030         }
1031       }
1032     }
1033     return pruned; // true if any interval was removed or modified by
1034     // operations.
1035   }
1036
1037   /**
1038    * remove any hiddenColumns or selected columns and shift remaining based on a
1039    * series of position, range deletions.
1040    * 
1041    * @param deletions
1042    */
1043   public void pruneDeletions(List<int[]> shifts)
1044   {
1045     // delete any intervals intersecting.
1046     if (hiddenColumns != null)
1047     {
1048       pruneIntervalVector(shifts, hiddenColumns);
1049       if (hiddenColumns != null && hiddenColumns.size() == 0)
1050       {
1051         hiddenColumns = null;
1052       }
1053     }
1054   }
1055
1056   /**
1057    * Returns a hashCode built from selected columns and hidden column ranges
1058    */
1059   public int hashCode(int hc)
1060   {
1061     int hashCode = hc;
1062     if (hiddenColumns != null)
1063     {
1064       for (int[] hidden : hiddenColumns)
1065       {
1066         hashCode = 31 * hashCode + hidden[0];
1067         hashCode = 31 * hashCode + hidden[1];
1068       }
1069     }
1070     return hashCode;
1071   }
1072
1073 }