JAL-2388 New hidden cols/seqs functions to support overview panel
[jalview.git] / src / jalview / datamodel / ColumnSelection.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.datamodel;
22
23 import jalview.util.Comparison;
24 import jalview.util.ShiftList;
25 import jalview.viewmodel.annotationfilter.AnnotationFilterParameter;
26 import jalview.viewmodel.annotationfilter.AnnotationFilterParameter.SearchableAnnotationField;
27
28 import java.util.ArrayList;
29 import java.util.BitSet;
30 import java.util.Collections;
31 import java.util.List;
32 import java.util.Vector;
33
34 /**
35  * Data class holding the selected columns and hidden column ranges for a view.
36  * Ranges are base 1.
37  */
38 public class ColumnSelection
39 {
40   /**
41    * A class to hold an efficient representation of selected columns
42    */
43   private class IntList
44   {
45     /*
46      * list of selected columns (ordered by selection order, not column order)
47      */
48     private List<Integer> order;
49
50     /*
51      * an unmodifiable view of the selected columns list
52      */
53     private List<Integer> _uorder;
54
55     /**
56      * bitfield for column selection - allows quick lookup
57      */
58     private BitSet selected;
59
60     /**
61      * Constructor
62      */
63     IntList()
64     {
65       order = new ArrayList<Integer>();
66       _uorder = Collections.unmodifiableList(order);
67       selected = new BitSet();
68     }
69
70     /**
71      * Copy constructor
72      * 
73      * @param other
74      */
75     IntList(IntList other)
76     {
77       this();
78       if (other != null)
79       {
80         int j = other.size();
81         for (int i = 0; i < j; i++)
82         {
83           add(other.elementAt(i));
84         }
85       }
86     }
87
88     /**
89      * adds a new column i to the selection - only if i is not already selected
90      * 
91      * @param i
92      */
93     void add(int i)
94     {
95       if (!selected.get(i))
96       {
97         order.add(Integer.valueOf(i));
98         selected.set(i);
99       }
100     }
101
102     void clear()
103     {
104       order.clear();
105       selected.clear();
106     }
107
108     void remove(int col)
109     {
110
111       Integer colInt = new Integer(col);
112
113       if (selected.get(col))
114       {
115         // if this ever changes to List.remove(), ensure Integer not int
116         // argument
117         // as List.remove(int i) removes the i'th item which is wrong
118         order.remove(colInt);
119         selected.clear(col);
120       }
121     }
122
123     boolean contains(Integer colInt)
124     {
125       return selected.get(colInt);
126     }
127
128     boolean isEmpty()
129     {
130       return order.isEmpty();
131     }
132
133     /**
134      * Returns a read-only view of the selected columns list
135      * 
136      * @return
137      */
138     List<Integer> getList()
139     {
140       return _uorder;
141     }
142
143     int size()
144     {
145       return order.size();
146     }
147
148     /**
149      * gets the column that was selected first, second or i'th
150      * 
151      * @param i
152      * @return
153      */
154     int elementAt(int i)
155     {
156       return order.get(i);
157     }
158
159     protected boolean pruneColumnList(final List<int[]> shifts)
160     {
161       int s = 0, t = shifts.size();
162       int[] sr = shifts.get(s++);
163       boolean pruned = false;
164       int i = 0, j = order.size();
165       while (i < j && s <= t)
166       {
167         int c = order.get(i++).intValue();
168         if (sr[0] <= c)
169         {
170           if (sr[1] + sr[0] >= c)
171           { // sr[1] -ve means inseriton.
172             order.remove(--i);
173             selected.clear(c);
174             j--;
175           }
176           else
177           {
178             if (s < t)
179             {
180               sr = shifts.get(s);
181             }
182             s++;
183           }
184         }
185       }
186       return pruned;
187     }
188
189     /**
190      * shift every selected column at or above start by change
191      * 
192      * @param start
193      *          - leftmost column to be shifted
194      * @param change
195      *          - delta for shift
196      */
197     void compensateForEdits(int start, int change)
198     {
199       BitSet mask = new BitSet();
200       for (int i = 0; i < order.size(); i++)
201       {
202         int temp = order.get(i);
203
204         if (temp >= start)
205         {
206           // clear shifted bits and update List of selected columns
207           selected.clear(temp);
208           mask.set(temp - change);
209           order.set(i, new Integer(temp - change));
210         }
211       }
212       // lastly update the bitfield all at once
213       selected.or(mask);
214     }
215
216     boolean isSelected(int column)
217     {
218       return selected.get(column);
219     }
220
221     int getMaxColumn()
222     {
223       return selected.length() - 1;
224     }
225
226     int getMinColumn()
227     {
228       return selected.get(0) ? 0 : selected.nextSetBit(0);
229     }
230
231     /**
232      * @return a series of selection intervals along the range
233      */
234     List<int[]> getRanges()
235     {
236       List<int[]> rlist = new ArrayList<int[]>();
237       if (selected.isEmpty())
238       {
239         return rlist;
240       }
241       int next = selected.nextSetBit(0), clear = -1;
242       while (next != -1)
243       {
244         clear = selected.nextClearBit(next);
245         rlist.add(new int[] { next, clear - 1 });
246         next = selected.nextSetBit(clear);
247       }
248       return rlist;
249     }
250
251     @Override
252     public int hashCode()
253     {
254       // TODO Auto-generated method stub
255       return selected.hashCode();
256     }
257
258     @Override
259     public boolean equals(Object obj)
260     {
261       if (obj instanceof IntList)
262       {
263         return ((IntList) obj).selected.equals(selected);
264       }
265       return false;
266     }
267   }
268
269   IntList selection = new IntList();
270
271   /*
272    * list of hidden column [start, end] ranges; the list is maintained in
273    * ascending start column order
274    */
275   Vector<int[]> hiddenColumns;
276
277   /**
278    * Add a column to the selection
279    * 
280    * @param col
281    *          index of column
282    */
283   public void addElement(int col)
284   {
285     selection.add(col);
286   }
287
288   /**
289    * clears column selection
290    */
291   public void clear()
292   {
293     selection.clear();
294   }
295
296   /**
297    * Removes value 'col' from the selection (not the col'th item)
298    * 
299    * @param col
300    *          index of column to be removed
301    */
302   public void removeElement(int col)
303   {
304     selection.remove(col);
305   }
306
307   /**
308    * removes a range of columns from the selection
309    * 
310    * @param start
311    *          int - first column in range to be removed
312    * @param end
313    *          int - last col
314    */
315   public void removeElements(int start, int end)
316   {
317     Integer colInt;
318     for (int i = start; i < end; i++)
319     {
320       colInt = new Integer(i);
321       if (selection.contains(colInt))
322       {
323         selection.remove(colInt);
324       }
325     }
326   }
327
328   /**
329    * Returns a read-only view of the (possibly empty) list of selected columns
330    * <p>
331    * The list contains no duplicates but is not necessarily ordered. It also may
332    * include columns hidden from the current view. To modify (for example sort)
333    * the list, you should first make a copy.
334    * <p>
335    * The list is not thread-safe: iterating over it could result in
336    * ConcurrentModificationException if it is modified by another thread.
337    */
338   public List<Integer> getSelected()
339   {
340     return selection.getList();
341   }
342
343   /**
344    * @return list of int arrays containing start and end column position for
345    *         runs of selected columns ordered from right to left.
346    */
347   public List<int[]> getSelectedRanges()
348   {
349     return selection.getRanges();
350   }
351
352   /**
353    * 
354    * @param col
355    *          index to search for in column selection
356    * 
357    * @return true if col is selected
358    */
359   public boolean contains(int col)
360   {
361     return (col > -1) ? selection.isSelected(col) : false;
362   }
363
364   /**
365    * Answers true if no columns are selected, else false
366    */
367   public boolean isEmpty()
368   {
369     return selection == null || selection.isEmpty();
370   }
371
372   /**
373    * rightmost selected column
374    * 
375    * @return rightmost column in alignment that is selected
376    */
377   public int getMax()
378   {
379     if (selection.isEmpty())
380     {
381       return -1;
382     }
383     return selection.getMaxColumn();
384   }
385
386   /**
387    * Leftmost column in selection
388    * 
389    * @return column index of leftmost column in selection
390    */
391   public int getMin()
392   {
393     if (selection.isEmpty())
394     {
395       return 1000000000;
396     }
397     return selection.getMinColumn();
398   }
399
400   /**
401    * propagate shift in alignment columns to column selection
402    * 
403    * @param start
404    *          beginning of edit
405    * @param left
406    *          shift in edit (+ve for removal, or -ve for inserts)
407    */
408   public List<int[]> compensateForEdit(int start, int change)
409   {
410     List<int[]> deletedHiddenColumns = null;
411     selection.compensateForEdits(start, change);
412
413     if (hiddenColumns != null)
414     {
415       deletedHiddenColumns = new ArrayList<int[]>();
416       int hSize = hiddenColumns.size();
417       for (int i = 0; i < hSize; i++)
418       {
419         int[] region = hiddenColumns.elementAt(i);
420         if (region[0] > start && start + change > region[1])
421         {
422           deletedHiddenColumns.add(region);
423
424           hiddenColumns.removeElementAt(i);
425           i--;
426           hSize--;
427           continue;
428         }
429
430         if (region[0] > start)
431         {
432           region[0] -= change;
433           region[1] -= change;
434         }
435
436         if (region[0] < 0)
437         {
438           region[0] = 0;
439         }
440
441       }
442
443       this.revealHiddenColumns(0);
444     }
445
446     return deletedHiddenColumns;
447   }
448
449   /**
450    * propagate shift in alignment columns to column selection special version of
451    * compensateForEdit - allowing for edits within hidden regions
452    * 
453    * @param start
454    *          beginning of edit
455    * @param left
456    *          shift in edit (+ve for removal, or -ve for inserts)
457    */
458   private void compensateForDelEdits(int start, int change)
459   {
460
461     selection.compensateForEdits(start, change);
462
463     if (hiddenColumns != null)
464     {
465       for (int i = 0; i < hiddenColumns.size(); i++)
466       {
467         int[] region = hiddenColumns.elementAt(i);
468         if (region[0] >= start)
469         {
470           region[0] -= change;
471         }
472         if (region[1] >= start)
473         {
474           region[1] -= change;
475         }
476         if (region[1] < region[0])
477         {
478           hiddenColumns.removeElementAt(i--);
479         }
480
481         if (region[0] < 0)
482         {
483           region[0] = 0;
484         }
485         if (region[1] < 0)
486         {
487           region[1] = 0;
488         }
489       }
490     }
491   }
492
493   /**
494    * Adjust hidden column boundaries based on a series of column additions or
495    * deletions in visible regions.
496    * 
497    * @param shiftrecord
498    * @return
499    */
500   public ShiftList compensateForEdits(ShiftList shiftrecord)
501   {
502     if (shiftrecord != null)
503     {
504       final List<int[]> shifts = shiftrecord.getShifts();
505       if (shifts != null && shifts.size() > 0)
506       {
507         int shifted = 0;
508         for (int i = 0, j = shifts.size(); i < j; i++)
509         {
510           int[] sh = shifts.get(i);
511           // compensateForEdit(shifted+sh[0], sh[1]);
512           compensateForDelEdits(shifted + sh[0], sh[1]);
513           shifted -= sh[1];
514         }
515       }
516       return shiftrecord.getInverse();
517     }
518     return null;
519   }
520
521   /**
522    * removes intersection of position,length ranges in deletions from the
523    * start,end regions marked in intervals.
524    * 
525    * @param shifts
526    * @param intervals
527    * @return
528    */
529   private boolean pruneIntervalVector(final List<int[]> shifts,
530           Vector<int[]> intervals)
531   {
532     boolean pruned = false;
533     int i = 0, j = intervals.size() - 1, s = 0, t = shifts.size() - 1;
534     int hr[] = intervals.elementAt(i);
535     int sr[] = shifts.get(s);
536     while (i <= j && s <= t)
537     {
538       boolean trailinghn = hr[1] >= sr[0];
539       if (!trailinghn)
540       {
541         if (i < j)
542         {
543           hr = intervals.elementAt(++i);
544         }
545         else
546         {
547           i++;
548         }
549         continue;
550       }
551       int endshift = sr[0] + sr[1]; // deletion ranges - -ve means an insert
552       if (endshift < hr[0] || endshift < sr[0])
553       { // leadinghc disjoint or not a deletion
554         if (s < t)
555         {
556           sr = shifts.get(++s);
557         }
558         else
559         {
560           s++;
561         }
562         continue;
563       }
564       boolean leadinghn = hr[0] >= sr[0];
565       boolean leadinghc = hr[0] < endshift;
566       boolean trailinghc = hr[1] < endshift;
567       if (leadinghn)
568       {
569         if (trailinghc)
570         { // deleted hidden region.
571           intervals.removeElementAt(i);
572           pruned = true;
573           j--;
574           if (i <= j)
575           {
576             hr = intervals.elementAt(i);
577           }
578           continue;
579         }
580         if (leadinghc)
581         {
582           hr[0] = endshift; // clip c terminal region
583           leadinghn = !leadinghn;
584           pruned = true;
585         }
586       }
587       if (!leadinghn)
588       {
589         if (trailinghc)
590         {
591           if (trailinghn)
592           {
593             hr[1] = sr[0] - 1;
594             pruned = true;
595           }
596         }
597         else
598         {
599           // sr contained in hr
600           if (s < t)
601           {
602             sr = shifts.get(++s);
603           }
604           else
605           {
606             s++;
607           }
608           continue;
609         }
610       }
611     }
612     return pruned; // true if any interval was removed or modified by
613     // operations.
614   }
615
616   /**
617    * remove any hiddenColumns or selected columns and shift remaining based on a
618    * series of position, range deletions.
619    * 
620    * @param deletions
621    */
622   public void pruneDeletions(ShiftList deletions)
623   {
624     if (deletions != null)
625     {
626       final List<int[]> shifts = deletions.getShifts();
627       if (shifts != null && shifts.size() > 0)
628       {
629         // delete any intervals intersecting.
630         if (hiddenColumns != null)
631         {
632           pruneIntervalVector(shifts, hiddenColumns);
633           if (hiddenColumns != null && hiddenColumns.size() == 0)
634           {
635             hiddenColumns = null;
636           }
637         }
638         if (selection != null && selection.size() > 0)
639         {
640           selection.pruneColumnList(shifts);
641           if (selection != null && selection.size() == 0)
642           {
643             selection = null;
644           }
645         }
646         // and shift the rest.
647         this.compensateForEdits(deletions);
648       }
649     }
650   }
651
652   /**
653    * This Method is used to return all the HiddenColumn regions
654    * 
655    * @return empty list or List of hidden column intervals
656    */
657   public List<int[]> getHiddenColumns()
658   {
659     return hiddenColumns == null ? Collections.<int[]> emptyList()
660             : hiddenColumns;
661   }
662
663   /**
664    * Return absolute column index for a visible column index
665    * 
666    * @param column
667    *          int column index in alignment view (count from zero)
668    * @return alignment column index for column
669    */
670   public int adjustForHiddenColumns(int column)
671   {
672     int result = column;
673     if (hiddenColumns != null)
674     {
675       for (int i = 0; i < hiddenColumns.size(); i++)
676       {
677         int[] region = hiddenColumns.elementAt(i);
678         if (result >= region[0])
679         {
680           result += region[1] - region[0] + 1;
681         }
682       }
683     }
684     return result;
685   }
686
687   /**
688    * Use this method to find out where a column will appear in the visible
689    * alignment when hidden columns exist. If the column is not visible, then the
690    * left-most visible column will always be returned.
691    * 
692    * @param hiddenColumn
693    *          the column index in the full alignment including hidden columns
694    * @return the position of the column in the visible alignment
695    */
696   public int findColumnPosition(int hiddenColumn)
697   {
698     int result = hiddenColumn;
699     if (hiddenColumns != null)
700     {
701       int index = 0;
702       int[] region;
703       do
704       {
705         region = hiddenColumns.elementAt(index++);
706         if (hiddenColumn > region[1])
707         {
708           result -= region[1] + 1 - region[0];
709         }
710       } while ((hiddenColumn > region[1]) && (index < hiddenColumns.size()));
711
712       if (hiddenColumn >= region[0] && hiddenColumn <= region[1])
713        {
714          // Here the hidden column is within a region, so
715          // we want to return the position of region[0]-1, adjusted for any
716          // earlier hidden columns.
717          // Calculate the difference between the actual hidden col position
718          // and region[0]-1, and then subtract from result to convert result from
719          // the adjusted hiddenColumn value to the adjusted region[0]-1 value
720
721         // However, if the region begins at 0 we cannot return region[0]-1
722         // just return 0
723         if (region[0] == 0)
724         {
725           return 0;
726         }
727         else
728         {
729           return result - (hiddenColumn - region[0] + 1);
730         }
731       }
732     }
733     return result; // return the shifted position after removing hidden columns.
734   }
735
736   /**
737    * Find the visible column which is a given visible number of columns to the
738    * left of another visible column. i.e. for a startColumn x, the column which
739    * is distance 1 away will be column x-1.
740    * 
741    * @param visibleDistance
742    *          the number of visible columns to offset by
743    * @param startColumn
744    *          the column to start from
745    * @return the position of the column in the visible alignment
746    */
747   public int findColumnNToLeft(int visibleDistance, int startColumn)
748   {
749     int distance = visibleDistance;
750
751     // in case startColumn is in a hidden region, move it to the left
752     int start = adjustForHiddenColumns(findColumnPosition(startColumn));
753
754     // get index of hidden region to left of start
755     int index = getHiddenIndexLeft(start);
756     if (index == -1)
757     {
758       // no hidden regions to left of startColumn
759       return start - distance;
760     }
761
762     // walk backwards through the alignment subtracting the counts of visible
763     // columns from distance
764     int[] region;
765     int gap = 0;
766     int nextstart = start;
767
768     while ((index > -1) && (distance - gap > 0))
769     {
770       // subtract the gap to right of region from distance
771       distance -= gap;
772       start = nextstart;
773
774       // calculate the next gap
775       region = hiddenColumns.get(index);
776       gap = start - region[1];
777
778       // set start to just to left of current region
779       nextstart = region[0] - 1;
780       index--;
781     }
782
783     if (distance - gap > 0)
784     {
785       // fell out of loop because there are no more hidden regions
786       distance -= gap;
787       return nextstart - distance;
788     }
789     return start - distance;
790
791   }
792
793   /**
794    * Use this method to determine where the next hiddenRegion starts
795    * 
796    * @param hiddenRegion
797    *          index of hidden region (counts from 0)
798    * @return column number in visible view
799    */
800   public int findHiddenRegionPosition(int hiddenRegion)
801   {
802     int result = 0;
803     if (hiddenColumns != null)
804     {
805       int index = 0;
806       int gaps = 0;
807       do
808       {
809         int[] region = hiddenColumns.elementAt(index);
810         if (hiddenRegion == 0)
811         {
812           return region[0];
813         }
814
815         gaps += region[1] + 1 - region[0];
816         result = region[1] + 1;
817         index++;
818       } while (index <= hiddenRegion);
819
820       result -= gaps;
821     }
822
823     return result;
824   }
825
826   /**
827    * THis method returns the rightmost limit of a region of an alignment with
828    * hidden columns. In otherwords, the next hidden column.
829    * 
830    * @param index
831    *          int
832    */
833   public int getHiddenBoundaryRight(int alPos)
834   {
835     if (hiddenColumns != null)
836     {
837       int index = 0;
838       do
839       {
840         int[] region = hiddenColumns.elementAt(index);
841         if (alPos < region[0])
842         {
843           return region[0];
844         }
845
846         index++;
847       } while (index < hiddenColumns.size());
848     }
849
850     return alPos;
851
852   }
853
854   /**
855    * This method returns the leftmost limit of a region of an alignment with
856    * hidden columns. In otherwords, the previous hidden column.
857    * 
858    * @param index
859    *          int
860    */
861   public int getHiddenBoundaryLeft(int alPos)
862   {
863     if (hiddenColumns != null)
864     {
865       int index = hiddenColumns.size() - 1;
866       do
867       {
868         int[] region = hiddenColumns.elementAt(index);
869         if (alPos > region[1])
870         {
871           return region[1];
872         }
873
874         index--;
875       } while (index > -1);
876     }
877
878     return alPos;
879
880   }
881
882   /**
883    * This method returns the index of the hidden region to the left of a column
884    * position. If the column is in a hidden region it returns the index of the
885    * region to the left. If there is no hidden region to the left it returns -1.
886    * 
887    * @param pos
888    *          int
889    */
890   private int getHiddenIndexLeft(int pos)
891   {
892     if (hiddenColumns != null)
893     {
894       int index = hiddenColumns.size() - 1;
895       do
896       {
897         int[] region = hiddenColumns.elementAt(index);
898         if (pos > region[1])
899         {
900           return index;
901         }
902
903         index--;
904       } while (index > -1);
905     }
906
907     return -1;
908
909   }
910
911   public void hideSelectedColumns()
912   {
913     synchronized (selection)
914     {
915       for (int[] selregions : selection.getRanges())
916       {
917         hideColumns(selregions[0], selregions[1]);
918       }
919       selection.clear();
920     }
921
922   }
923
924   /**
925    * Adds the specified column range to the hidden columns
926    * 
927    * @param start
928    * @param end
929    */
930   public void hideColumns(int start, int end)
931   {
932     if (hiddenColumns == null)
933     {
934       hiddenColumns = new Vector<int[]>();
935     }
936
937     /*
938      * traverse existing hidden ranges and insert / amend / append as
939      * appropriate
940      */
941     for (int i = 0; i < hiddenColumns.size(); i++)
942     {
943       int[] region = hiddenColumns.elementAt(i);
944
945       if (end < region[0] - 1)
946       {
947         /*
948          * insert discontiguous preceding range
949          */
950         hiddenColumns.insertElementAt(new int[] { start, end }, i);
951         return;
952       }
953
954       if (end <= region[1])
955       {
956         /*
957          * new range overlaps existing, or is contiguous preceding it - adjust
958          * start column
959          */
960         region[0] = Math.min(region[0], start);
961         return;
962       }
963
964       if (start <= region[1] + 1)
965       {
966         /*
967          * new range overlaps existing, or is contiguous following it - adjust
968          * start and end columns
969          */
970         region[0] = Math.min(region[0], start);
971         region[1] = Math.max(region[1], end);
972
973         /*
974          * also update or remove any subsequent ranges 
975          * that are overlapped
976          */
977         while (i < hiddenColumns.size() - 1)
978         {
979           int[] nextRegion = hiddenColumns.get(i + 1);
980           if (nextRegion[0] > end + 1)
981           {
982             /*
983              * gap to next hidden range - no more to update
984              */
985             break;
986           }
987           region[1] = Math.max(nextRegion[1], end);
988           hiddenColumns.remove(i + 1);
989         }
990         return;
991       }
992     }
993
994     /*
995      * remaining case is that the new range follows everything else
996      */
997     hiddenColumns.addElement(new int[] { start, end });
998   }
999
1000   /**
1001    * Hides the specified column and any adjacent selected columns
1002    * 
1003    * @param res
1004    *          int
1005    */
1006   public void hideColumns(int col)
1007   {
1008     /*
1009      * deselect column (whether selected or not!)
1010      */
1011     removeElement(col);
1012
1013     /*
1014      * find adjacent selected columns
1015      */
1016     int min = col - 1, max = col + 1;
1017     while (contains(min))
1018     {
1019       removeElement(min);
1020       min--;
1021     }
1022
1023     while (contains(max))
1024     {
1025       removeElement(max);
1026       max++;
1027     }
1028
1029     /*
1030      * min, max are now the closest unselected columns
1031      */
1032     min++;
1033     max--;
1034     if (min > max)
1035     {
1036       min = max;
1037     }
1038
1039     hideColumns(min, max);
1040   }
1041
1042   /**
1043    * Unhides, and adds to the selection list, all hidden columns
1044    */
1045   public void revealAllHiddenColumns()
1046   {
1047     if (hiddenColumns != null)
1048     {
1049       for (int i = 0; i < hiddenColumns.size(); i++)
1050       {
1051         int[] region = hiddenColumns.elementAt(i);
1052         for (int j = region[0]; j < region[1] + 1; j++)
1053         {
1054           addElement(j);
1055         }
1056       }
1057     }
1058
1059     hiddenColumns = null;
1060   }
1061
1062   /**
1063    * Reveals, and marks as selected, the hidden column range with the given
1064    * start column
1065    * 
1066    * @param start
1067    */
1068   public void revealHiddenColumns(int start)
1069   {
1070     for (int i = 0; i < hiddenColumns.size(); i++)
1071     {
1072       int[] region = hiddenColumns.elementAt(i);
1073       if (start == region[0])
1074       {
1075         for (int j = region[0]; j < region[1] + 1; j++)
1076         {
1077           addElement(j);
1078         }
1079
1080         hiddenColumns.removeElement(region);
1081         break;
1082       }
1083     }
1084     if (hiddenColumns.size() == 0)
1085     {
1086       hiddenColumns = null;
1087     }
1088   }
1089
1090   public boolean isVisible(int column)
1091   {
1092     if (hiddenColumns != null)
1093     {
1094       for (int[] region : hiddenColumns)
1095       {
1096         if (column >= region[0] && column <= region[1])
1097         {
1098           return false;
1099         }
1100       }
1101     }
1102
1103     return true;
1104   }
1105
1106   /**
1107    * Copy constructor
1108    * 
1109    * @param copy
1110    */
1111   public ColumnSelection(ColumnSelection copy)
1112   {
1113     if (copy != null)
1114     {
1115       selection = new IntList(copy.selection);
1116       if (copy.hiddenColumns != null)
1117       {
1118         hiddenColumns = new Vector<int[]>(copy.hiddenColumns.size());
1119         for (int i = 0, j = copy.hiddenColumns.size(); i < j; i++)
1120         {
1121           int[] rh, cp;
1122           rh = copy.hiddenColumns.elementAt(i);
1123           if (rh != null)
1124           {
1125             cp = new int[rh.length];
1126             System.arraycopy(rh, 0, cp, 0, rh.length);
1127             hiddenColumns.addElement(cp);
1128           }
1129         }
1130       }
1131     }
1132   }
1133
1134   /**
1135    * ColumnSelection
1136    */
1137   public ColumnSelection()
1138   {
1139   }
1140
1141   public String[] getVisibleSequenceStrings(int start, int end,
1142           SequenceI[] seqs)
1143   {
1144     int i, iSize = seqs.length;
1145     String selections[] = new String[iSize];
1146     if (hiddenColumns != null && hiddenColumns.size() > 0)
1147     {
1148       for (i = 0; i < iSize; i++)
1149       {
1150         StringBuffer visibleSeq = new StringBuffer();
1151         List<int[]> regions = getHiddenColumns();
1152
1153         int blockStart = start, blockEnd = end;
1154         int[] region;
1155         int hideStart, hideEnd;
1156
1157         for (int j = 0; j < regions.size(); j++)
1158         {
1159           region = regions.get(j);
1160           hideStart = region[0];
1161           hideEnd = region[1];
1162
1163           if (hideStart < start)
1164           {
1165             continue;
1166           }
1167
1168           blockStart = Math.min(blockStart, hideEnd + 1);
1169           blockEnd = Math.min(blockEnd, hideStart);
1170
1171           if (blockStart > blockEnd)
1172           {
1173             break;
1174           }
1175
1176           visibleSeq.append(seqs[i].getSequence(blockStart, blockEnd));
1177
1178           blockStart = hideEnd + 1;
1179           blockEnd = end;
1180         }
1181
1182         if (end > blockStart)
1183         {
1184           visibleSeq.append(seqs[i].getSequence(blockStart, end));
1185         }
1186
1187         selections[i] = visibleSeq.toString();
1188       }
1189     }
1190     else
1191     {
1192       for (i = 0; i < iSize; i++)
1193       {
1194         selections[i] = seqs[i].getSequenceAsString(start, end);
1195       }
1196     }
1197
1198     return selections;
1199   }
1200
1201   /**
1202    * return all visible segments between the given start and end boundaries
1203    * 
1204    * @param start
1205    *          (first column inclusive from 0)
1206    * @param end
1207    *          (last column - not inclusive)
1208    * @return int[] {i_start, i_end, ..} where intervals lie in
1209    *         start<=i_start<=i_end<end
1210    */
1211   public int[] getVisibleContigs(int start, int end)
1212   {
1213     if (hiddenColumns != null && hiddenColumns.size() > 0)
1214     {
1215       List<int[]> visiblecontigs = new ArrayList<int[]>();
1216       List<int[]> regions = getHiddenColumns();
1217
1218       int vstart = start;
1219       int[] region;
1220       int hideStart, hideEnd;
1221
1222       for (int j = 0; vstart < end && j < regions.size(); j++)
1223       {
1224         region = regions.get(j);
1225         hideStart = region[0];
1226         hideEnd = region[1];
1227
1228         if (hideEnd < vstart)
1229         {
1230           continue;
1231         }
1232         if (hideStart > vstart)
1233         {
1234           visiblecontigs.add(new int[] { vstart, hideStart - 1 });
1235         }
1236         vstart = hideEnd + 1;
1237       }
1238
1239       if (vstart < end)
1240       {
1241         visiblecontigs.add(new int[] { vstart, end - 1 });
1242       }
1243       int[] vcontigs = new int[visiblecontigs.size() * 2];
1244       for (int i = 0, j = visiblecontigs.size(); i < j; i++)
1245       {
1246         int[] vc = visiblecontigs.get(i);
1247         visiblecontigs.set(i, null);
1248         vcontigs[i * 2] = vc[0];
1249         vcontigs[i * 2 + 1] = vc[1];
1250       }
1251       visiblecontigs.clear();
1252       return vcontigs;
1253     }
1254     else
1255     {
1256       return new int[] { start, end - 1 };
1257     }
1258   }
1259
1260   /**
1261    * Locate the first and last position visible for this sequence. if seq isn't
1262    * visible then return the position of the left and right of the hidden
1263    * boundary region, and the corresponding alignment column indices for the
1264    * extent of the sequence
1265    * 
1266    * @param seq
1267    * @return int[] { visible start, visible end, first seqpos, last seqpos,
1268    *         alignment index for seq start, alignment index for seq end }
1269    */
1270   public int[] locateVisibleBoundsOfSequence(SequenceI seq)
1271   {
1272     int fpos = seq.getStart(), lpos = seq.getEnd();
1273     int start = 0;
1274
1275     if (hiddenColumns == null || hiddenColumns.size() == 0)
1276     {
1277       int ifpos = seq.findIndex(fpos) - 1, ilpos = seq.findIndex(lpos) - 1;
1278       return new int[] { ifpos, ilpos, fpos, lpos, ifpos, ilpos };
1279     }
1280
1281     // Simply walk along the sequence whilst watching for hidden column
1282     // boundaries
1283     List<int[]> regions = getHiddenColumns();
1284     int spos = fpos, lastvispos = -1, rcount = 0, hideStart = seq
1285             .getLength(), hideEnd = -1;
1286     int visPrev = 0, visNext = 0, firstP = -1, lastP = -1;
1287     boolean foundStart = false;
1288     for (int p = 0, pLen = seq.getLength(); spos <= seq.getEnd()
1289             && p < pLen; p++)
1290     {
1291       if (!Comparison.isGap(seq.getCharAt(p)))
1292       {
1293         // keep track of first/last column
1294         // containing sequence data regardless of visibility
1295         if (firstP == -1)
1296         {
1297           firstP = p;
1298         }
1299         lastP = p;
1300         // update hidden region start/end
1301         while (hideEnd < p && rcount < regions.size())
1302         {
1303           int[] region = regions.get(rcount++);
1304           visPrev = visNext;
1305           visNext += region[0] - visPrev;
1306           hideStart = region[0];
1307           hideEnd = region[1];
1308         }
1309         if (hideEnd < p)
1310         {
1311           hideStart = seq.getLength();
1312         }
1313         // update visible boundary for sequence
1314         if (p < hideStart)
1315         {
1316           if (!foundStart)
1317           {
1318             fpos = spos;
1319             start = p;
1320             foundStart = true;
1321           }
1322           lastvispos = p;
1323           lpos = spos;
1324         }
1325         // look for next sequence position
1326         spos++;
1327       }
1328     }
1329     if (foundStart)
1330     {
1331       return new int[] { findColumnPosition(start),
1332           findColumnPosition(lastvispos), fpos, lpos, firstP, lastP };
1333     }
1334     // otherwise, sequence was completely hidden
1335     return new int[] { visPrev, visNext, 0, 0, firstP, lastP };
1336   }
1337
1338   /**
1339    * delete any columns in alignmentAnnotation that are hidden (including
1340    * sequence associated annotation).
1341    * 
1342    * @param alignmentAnnotation
1343    */
1344   public void makeVisibleAnnotation(AlignmentAnnotation alignmentAnnotation)
1345   {
1346     makeVisibleAnnotation(-1, -1, alignmentAnnotation);
1347   }
1348
1349   /**
1350    * delete any columns in alignmentAnnotation that are hidden (including
1351    * sequence associated annotation).
1352    * 
1353    * @param start
1354    *          remove any annotation to the right of this column
1355    * @param end
1356    *          remove any annotation to the left of this column
1357    * @param alignmentAnnotation
1358    *          the annotation to operate on
1359    */
1360   public void makeVisibleAnnotation(int start, int end,
1361           AlignmentAnnotation alignmentAnnotation)
1362   {
1363     if (alignmentAnnotation.annotations == null)
1364     {
1365       return;
1366     }
1367     if (start == end && end == -1)
1368     {
1369       start = 0;
1370       end = alignmentAnnotation.annotations.length;
1371     }
1372     if (hiddenColumns != null && hiddenColumns.size() > 0)
1373     {
1374       // then mangle the alignmentAnnotation annotation array
1375       Vector<Annotation[]> annels = new Vector<Annotation[]>();
1376       Annotation[] els = null;
1377       List<int[]> regions = getHiddenColumns();
1378       int blockStart = start, blockEnd = end;
1379       int[] region;
1380       int hideStart, hideEnd, w = 0;
1381
1382       for (int j = 0; j < regions.size(); j++)
1383       {
1384         region = regions.get(j);
1385         hideStart = region[0];
1386         hideEnd = region[1];
1387
1388         if (hideStart < start)
1389         {
1390           continue;
1391         }
1392
1393         blockStart = Math.min(blockStart, hideEnd + 1);
1394         blockEnd = Math.min(blockEnd, hideStart);
1395
1396         if (blockStart > blockEnd)
1397         {
1398           break;
1399         }
1400
1401         annels.addElement(els = new Annotation[blockEnd - blockStart]);
1402         System.arraycopy(alignmentAnnotation.annotations, blockStart, els,
1403                 0, els.length);
1404         w += els.length;
1405         blockStart = hideEnd + 1;
1406         blockEnd = end;
1407       }
1408
1409       if (end > blockStart)
1410       {
1411         annels.addElement(els = new Annotation[end - blockStart + 1]);
1412         if ((els.length + blockStart) <= alignmentAnnotation.annotations.length)
1413         {
1414           // copy just the visible segment of the annotation row
1415           System.arraycopy(alignmentAnnotation.annotations, blockStart,
1416                   els, 0, els.length);
1417         }
1418         else
1419         {
1420           // copy to the end of the annotation row
1421           System.arraycopy(alignmentAnnotation.annotations, blockStart,
1422                   els, 0,
1423                   (alignmentAnnotation.annotations.length - blockStart));
1424         }
1425         w += els.length;
1426       }
1427       if (w == 0)
1428       {
1429         return;
1430       }
1431
1432       alignmentAnnotation.annotations = new Annotation[w];
1433       w = 0;
1434
1435       for (Annotation[] chnk : annels)
1436       {
1437         System.arraycopy(chnk, 0, alignmentAnnotation.annotations, w,
1438                 chnk.length);
1439         w += chnk.length;
1440       }
1441     }
1442     else
1443     {
1444       alignmentAnnotation.restrict(start, end);
1445     }
1446   }
1447
1448   /**
1449    * Invert the column selection from first to end-1. leaves hiddenColumns
1450    * untouched (and unselected)
1451    * 
1452    * @param first
1453    * @param end
1454    */
1455   public void invertColumnSelection(int first, int width)
1456   {
1457     boolean hasHidden = hiddenColumns != null && hiddenColumns.size() > 0;
1458     for (int i = first; i < width; i++)
1459     {
1460       if (contains(i))
1461       {
1462         removeElement(i);
1463       }
1464       else
1465       {
1466         if (!hasHidden || isVisible(i))
1467         {
1468           addElement(i);
1469         }
1470       }
1471     }
1472   }
1473
1474   /**
1475    * add in any unselected columns from the given column selection, excluding
1476    * any that are hidden.
1477    * 
1478    * @param colsel
1479    */
1480   public void addElementsFrom(ColumnSelection colsel)
1481   {
1482     if (colsel != null && !colsel.isEmpty())
1483     {
1484       for (Integer col : colsel.getSelected())
1485       {
1486         if (hiddenColumns != null && isVisible(col.intValue()))
1487         {
1488           selection.add(col);
1489         }
1490       }
1491     }
1492   }
1493
1494   /**
1495    * set the selected columns the given column selection, excluding any columns
1496    * that are hidden.
1497    * 
1498    * @param colsel
1499    */
1500   public void setElementsFrom(ColumnSelection colsel)
1501   {
1502     selection = new IntList();
1503     if (colsel.selection != null && colsel.selection.size() > 0)
1504     {
1505       if (hiddenColumns != null && hiddenColumns.size() > 0)
1506       {
1507         // only select visible columns in this columns selection
1508         addElementsFrom(colsel);
1509       }
1510       else
1511       {
1512         // add everything regardless
1513         for (Integer col : colsel.getSelected())
1514         {
1515           addElement(col);
1516         }
1517       }
1518     }
1519   }
1520
1521   /**
1522    * Add gaps into the sequences aligned to profileseq under the given
1523    * AlignmentView
1524    * 
1525    * @param profileseq
1526    * @param al
1527    *          - alignment to have gaps inserted into it
1528    * @param input
1529    *          - alignment view where sequence corresponding to profileseq is
1530    *          first entry
1531    * @return new Column selection for new alignment view, with insertions into
1532    *         profileseq marked as hidden.
1533    */
1534   public static ColumnSelection propagateInsertions(SequenceI profileseq,
1535           AlignmentI al, AlignmentView input)
1536   {
1537     int profsqpos = 0;
1538
1539     // return propagateInsertions(profileseq, al, )
1540     char gc = al.getGapCharacter();
1541     Object[] alandcolsel = input.getAlignmentAndColumnSelection(gc);
1542     ColumnSelection nview = (ColumnSelection) alandcolsel[1];
1543     SequenceI origseq = ((SequenceI[]) alandcolsel[0])[profsqpos];
1544     nview.propagateInsertions(profileseq, al, origseq);
1545     return nview;
1546   }
1547
1548   /**
1549    * 
1550    * @param profileseq
1551    *          - sequence in al which corresponds to origseq
1552    * @param al
1553    *          - alignment which is to have gaps inserted into it
1554    * @param origseq
1555    *          - sequence corresponding to profileseq which defines gap map for
1556    *          modifying al
1557    */
1558   public void propagateInsertions(SequenceI profileseq, AlignmentI al,
1559           SequenceI origseq)
1560   {
1561     char gc = al.getGapCharacter();
1562     // recover mapping between sequence's non-gap positions and positions
1563     // mapping to view.
1564     pruneDeletions(ShiftList.parseMap(origseq.gapMap()));
1565     int[] viscontigs = getVisibleContigs(0, profileseq.getLength());
1566     int spos = 0;
1567     int offset = 0;
1568     // input.pruneDeletions(ShiftList.parseMap(((SequenceI[])
1569     // alandcolsel[0])[0].gapMap()))
1570     // add profile to visible contigs
1571     for (int v = 0; v < viscontigs.length; v += 2)
1572     {
1573       if (viscontigs[v] > spos)
1574       {
1575         StringBuffer sb = new StringBuffer();
1576         for (int s = 0, ns = viscontigs[v] - spos; s < ns; s++)
1577         {
1578           sb.append(gc);
1579         }
1580         for (int s = 0, ns = al.getHeight(); s < ns; s++)
1581         {
1582           SequenceI sqobj = al.getSequenceAt(s);
1583           if (sqobj != profileseq)
1584           {
1585             String sq = al.getSequenceAt(s).getSequenceAsString();
1586             if (sq.length() <= spos + offset)
1587             {
1588               // pad sequence
1589               int diff = spos + offset - sq.length() - 1;
1590               if (diff > 0)
1591               {
1592                 // pad gaps
1593                 sq = sq + sb;
1594                 while ((diff = spos + offset - sq.length() - 1) > 0)
1595                 {
1596                   // sq = sq
1597                   // + ((diff >= sb.length()) ? sb.toString() : sb
1598                   // .substring(0, diff));
1599                   if (diff >= sb.length())
1600                   {
1601                     sq += sb.toString();
1602                   }
1603                   else
1604                   {
1605                     char[] buf = new char[diff];
1606                     sb.getChars(0, diff, buf, 0);
1607                     sq += buf.toString();
1608                   }
1609                 }
1610               }
1611               sq += sb.toString();
1612             }
1613             else
1614             {
1615               al.getSequenceAt(s).setSequence(
1616                       sq.substring(0, spos + offset) + sb.toString()
1617                               + sq.substring(spos + offset));
1618             }
1619           }
1620         }
1621         // offset+=sb.length();
1622       }
1623       spos = viscontigs[v + 1] + 1;
1624     }
1625     if ((offset + spos) < profileseq.getLength())
1626     {
1627       // pad the final region with gaps.
1628       StringBuffer sb = new StringBuffer();
1629       for (int s = 0, ns = profileseq.getLength() - spos - offset; s < ns; s++)
1630       {
1631         sb.append(gc);
1632       }
1633       for (int s = 0, ns = al.getHeight(); s < ns; s++)
1634       {
1635         SequenceI sqobj = al.getSequenceAt(s);
1636         if (sqobj == profileseq)
1637         {
1638           continue;
1639         }
1640         String sq = sqobj.getSequenceAsString();
1641         // pad sequence
1642         int diff = origseq.getLength() - sq.length();
1643         while (diff > 0)
1644         {
1645           // sq = sq
1646           // + ((diff >= sb.length()) ? sb.toString() : sb
1647           // .substring(0, diff));
1648           if (diff >= sb.length())
1649           {
1650             sq += sb.toString();
1651           }
1652           else
1653           {
1654             char[] buf = new char[diff];
1655             sb.getChars(0, diff, buf, 0);
1656             sq += buf.toString();
1657           }
1658           diff = origseq.getLength() - sq.length();
1659         }
1660       }
1661     }
1662   }
1663
1664   /**
1665    * 
1666    * @return true if there are columns marked
1667    */
1668   public boolean hasSelectedColumns()
1669   {
1670     return (selection != null && selection.size() > 0);
1671   }
1672
1673   /**
1674    * 
1675    * @return true if there are columns hidden
1676    */
1677   public boolean hasHiddenColumns()
1678   {
1679     return hiddenColumns != null && hiddenColumns.size() > 0;
1680   }
1681
1682   /**
1683    * 
1684    * @return true if there are more than one set of columns hidden
1685    */
1686   public boolean hasManyHiddenColumns()
1687   {
1688     return hiddenColumns != null && hiddenColumns.size() > 1;
1689   }
1690
1691   /**
1692    * mark the columns corresponding to gap characters as hidden in the column
1693    * selection
1694    * 
1695    * @param sr
1696    */
1697   public void hideInsertionsFor(SequenceI sr)
1698   {
1699     List<int[]> inserts = sr.getInsertions();
1700     for (int[] r : inserts)
1701     {
1702       hideColumns(r[0], r[1]);
1703     }
1704   }
1705
1706   public boolean filterAnnotations(Annotation[] annotations,
1707           AnnotationFilterParameter filterParams)
1708   {
1709     // JBPNote - this method needs to be refactored to become independent of
1710     // viewmodel package
1711     this.revealAllHiddenColumns();
1712     this.clear();
1713     int count = 0;
1714     do
1715     {
1716       if (annotations[count] != null)
1717       {
1718
1719         boolean itemMatched = false;
1720
1721         if (filterParams.getThresholdType() == AnnotationFilterParameter.ThresholdType.ABOVE_THRESHOLD
1722                 && annotations[count].value >= filterParams
1723                         .getThresholdValue())
1724         {
1725           itemMatched = true;
1726         }
1727         if (filterParams.getThresholdType() == AnnotationFilterParameter.ThresholdType.BELOW_THRESHOLD
1728                 && annotations[count].value <= filterParams
1729                         .getThresholdValue())
1730         {
1731           itemMatched = true;
1732         }
1733
1734         if (filterParams.isFilterAlphaHelix()
1735                 && annotations[count].secondaryStructure == 'H')
1736         {
1737           itemMatched = true;
1738         }
1739
1740         if (filterParams.isFilterBetaSheet()
1741                 && annotations[count].secondaryStructure == 'E')
1742         {
1743           itemMatched = true;
1744         }
1745
1746         if (filterParams.isFilterTurn()
1747                 && annotations[count].secondaryStructure == 'S')
1748         {
1749           itemMatched = true;
1750         }
1751
1752         String regexSearchString = filterParams.getRegexString();
1753         if (regexSearchString != null
1754                 && !filterParams.getRegexSearchFields().isEmpty())
1755         {
1756           List<SearchableAnnotationField> fields = filterParams
1757                   .getRegexSearchFields();
1758           try
1759           {
1760             if (fields.contains(SearchableAnnotationField.DISPLAY_STRING)
1761                     && annotations[count].displayCharacter
1762                             .matches(regexSearchString))
1763             {
1764               itemMatched = true;
1765             }
1766           } catch (java.util.regex.PatternSyntaxException pse)
1767           {
1768             if (annotations[count].displayCharacter
1769                     .equals(regexSearchString))
1770             {
1771               itemMatched = true;
1772             }
1773           }
1774           if (fields.contains(SearchableAnnotationField.DESCRIPTION)
1775                   && annotations[count].description != null
1776                   && annotations[count].description
1777                           .matches(regexSearchString))
1778           {
1779             itemMatched = true;
1780           }
1781         }
1782
1783         if (itemMatched)
1784         {
1785           this.addElement(count);
1786         }
1787       }
1788       count++;
1789     } while (count < annotations.length);
1790     return false;
1791   }
1792
1793   /**
1794    * Returns a hashCode built from selected columns and hidden column ranges
1795    */
1796   @Override
1797   public int hashCode()
1798   {
1799     int hashCode = selection.hashCode();
1800     if (hiddenColumns != null)
1801     {
1802       for (int[] hidden : hiddenColumns)
1803       {
1804         hashCode = 31 * hashCode + hidden[0];
1805         hashCode = 31 * hashCode + hidden[1];
1806       }
1807     }
1808     return hashCode;
1809   }
1810
1811   /**
1812    * Answers true if comparing to a ColumnSelection with the same selected
1813    * columns and hidden columns, else false
1814    */
1815   @Override
1816   public boolean equals(Object obj)
1817   {
1818     if (!(obj instanceof ColumnSelection))
1819     {
1820       return false;
1821     }
1822     ColumnSelection that = (ColumnSelection) obj;
1823
1824     /*
1825      * check columns selected are either both null, or match
1826      */
1827     if (this.selection == null)
1828     {
1829       if (that.selection != null)
1830       {
1831         return false;
1832       }
1833     }
1834     if (!this.selection.equals(that.selection))
1835     {
1836       return false;
1837     }
1838
1839     /*
1840      * check hidden columns are either both null, or match
1841      */
1842     if (this.hiddenColumns == null)
1843     {
1844       return (that.hiddenColumns == null);
1845     }
1846     if (that.hiddenColumns == null
1847             || that.hiddenColumns.size() != this.hiddenColumns.size())
1848     {
1849       return false;
1850     }
1851     int i = 0;
1852     for (int[] thisRange : hiddenColumns)
1853     {
1854       int[] thatRange = that.hiddenColumns.get(i++);
1855       if (thisRange[0] != thatRange[0] || thisRange[1] != thatRange[1])
1856       {
1857         return false;
1858       }
1859     }
1860     return true;
1861   }
1862
1863   /**
1864    * Updates the column selection depending on the parameters, and returns true
1865    * if any change was made to the selection
1866    * 
1867    * @param markedColumns
1868    *          a set identifying marked columns (base 0)
1869    * @param startCol
1870    *          the first column of the range to operate over (base 0)
1871    * @param endCol
1872    *          the last column of the range to operate over (base 0)
1873    * @param invert
1874    *          if true, deselect marked columns and select unmarked
1875    * @param extendCurrent
1876    *          if true, extend rather than replacing the current column selection
1877    * @param toggle
1878    *          if true, toggle the selection state of marked columns
1879    * 
1880    * @return
1881    */
1882   public boolean markColumns(BitSet markedColumns, int startCol,
1883           int endCol, boolean invert, boolean extendCurrent, boolean toggle)
1884   {
1885     boolean changed = false;
1886     if (!extendCurrent && !toggle)
1887     {
1888       changed = !this.isEmpty();
1889       clear();
1890     }
1891     if (invert)
1892     {
1893       // invert only in the currently selected sequence region
1894       int i = markedColumns.nextClearBit(startCol);
1895       int ibs = markedColumns.nextSetBit(startCol);
1896       while (i >= startCol && i <= endCol)
1897       {
1898         if (ibs < 0 || i < ibs)
1899         {
1900           changed = true;
1901           if (toggle && contains(i))
1902           {
1903             removeElement(i++);
1904           }
1905           else
1906           {
1907             addElement(i++);
1908           }
1909         }
1910         else
1911         {
1912           i = markedColumns.nextClearBit(ibs);
1913           ibs = markedColumns.nextSetBit(i);
1914         }
1915       }
1916     }
1917     else
1918     {
1919       int i = markedColumns.nextSetBit(startCol);
1920       while (i >= startCol && i <= endCol)
1921       {
1922         changed = true;
1923         if (toggle && contains(i))
1924         {
1925           removeElement(i);
1926         }
1927         else
1928         {
1929           addElement(i);
1930         }
1931         i = markedColumns.nextSetBit(i + 1);
1932       }
1933     }
1934     return changed;
1935   }
1936
1937   /**
1938    * Adjusts column selections, and the given selection group, to match the
1939    * range of a stretch (e.g. mouse drag) operation
1940    * <p>
1941    * Method refactored from ScalePanel.mouseDragged
1942    * 
1943    * @param res
1944    *          current column position, adjusted for hidden columns
1945    * @param sg
1946    *          current selection group
1947    * @param min
1948    *          start position of the stretch group
1949    * @param max
1950    *          end position of the stretch group
1951    */
1952   public void stretchGroup(int res, SequenceGroup sg, int min, int max)
1953   {
1954     if (!contains(res))
1955     {
1956       addElement(res);
1957     }
1958
1959     if (res > sg.getStartRes())
1960     {
1961       // expand selection group to the right
1962       sg.setEndRes(res);
1963     }
1964     if (res < sg.getStartRes())
1965     {
1966       // expand selection group to the left
1967       sg.setStartRes(res);
1968     }
1969
1970     /*
1971      * expand or shrink column selection to match the
1972      * range of the drag operation
1973      */
1974     for (int col = min; col <= max; col++)
1975     {
1976       if (col < sg.getStartRes() || col > sg.getEndRes())
1977       {
1978         // shrinking drag - remove from selection
1979         removeElement(col);
1980       }
1981       else
1982       {
1983         // expanding drag - add to selection
1984         addElement(col);
1985       }
1986     }
1987   }
1988 }