JAL-966 JAL-1553
 todo: remove UI components from Column Selection code
[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  * NOTE: Columns are zero based.
36  */
37 public class ColumnSelection
38 {
39   private class IntList
40   {
41     /*
42      * list of selected columns (ordered by selection order, not column order)
43      */
44     private List<Integer> order = new ArrayList<Integer>();
45
46     /**
47      * bitfield for column selection - allows quick lookup
48      */
49     private BitSet selected = new BitSet();
50
51     /**
52      * adds a new column i to the selection - only if i is not already selected
53      * 
54      * @param i
55      */
56     public void add(int i)
57     {
58       if (!selected.get(i))
59       {
60         order.add(Integer.valueOf(i));
61         selected.set(i);
62       }
63     }
64
65     public void clear()
66     {
67       order.clear();
68       selected.clear();
69     }
70
71     public void remove(int col)
72     {
73
74       Integer colInt = new Integer(col);
75
76       if (selected.get(col))
77       {
78         // if this ever changes to List.remove(), ensure Integer not int
79         // argument
80         // as List.remove(int i) removes the i'th item which is wrong
81         order.remove(colInt);
82         selected.clear(col);
83       }
84     }
85
86     public boolean contains(Integer colInt)
87     {
88       return selected.get(colInt);
89     }
90
91     public boolean isEmpty()
92     {
93       return order.isEmpty();
94     }
95
96     public List<Integer> getList()
97     {
98       return order;
99     }
100
101     public int size()
102     {
103       return order.size();
104     }
105
106     /**
107      * gets the column that was selected first, second or i'th
108      * 
109      * @param i
110      * @return
111      */
112     public int elementAt(int i)
113     {
114       return order.get(i);
115     }
116
117     protected boolean pruneColumnList(final List<int[]> shifts)
118     {
119       int s = 0, t = shifts.size();
120       int[] sr = shifts.get(s++);
121       boolean pruned = false;
122       int i = 0, j = order.size();
123       while (i < j && s <= t)
124       {
125         int c = order.get(i++).intValue();
126         if (sr[0] <= c)
127         {
128           if (sr[1] + sr[0] >= c)
129           { // sr[1] -ve means inseriton.
130             order.remove(--i);
131             selected.clear(c);
132             j--;
133           }
134           else
135           {
136             if (s < t)
137             {
138               sr = shifts.get(s);
139             }
140             s++;
141           }
142         }
143       }
144       return pruned;
145     }
146
147     /**
148      * shift every selected column at or above start by change
149      * 
150      * @param start
151      *          - leftmost column to be shifted
152      * @param change
153      *          - delta for shift
154      */
155     public void compensateForEdits(int start, int change)
156     {
157       BitSet mask = new BitSet();
158       for (int i = 0; i < order.size(); i++)
159       {
160         int temp = order.get(i);
161
162         if (temp >= start)
163         {
164           // clear shifted bits and update List of selected columns
165           selected.clear(temp);
166           mask.set(temp - change);
167           order.set(i, new Integer(temp - change));
168         }
169       }
170       // lastly update the bitfield all at once
171       selected.or(mask);
172     }
173
174     public boolean isSelected(int column)
175     {
176       return selected.get(column);
177     }
178
179     public int getMaxColumn()
180     {
181       return selected.length() - 1;
182     }
183
184     public int getMinColumn()
185     {
186       return selected.get(0) ? 0 : selected.nextSetBit(0);
187     }
188
189     /**
190      * @return a series of selection intervals along the range
191      */
192     public List<int[]> getRanges()
193     {
194       List<int[]> rlist = new ArrayList<int[]>();
195       if (selected.isEmpty())
196       {
197         return rlist;
198       }
199       int next = selected.nextSetBit(0), clear = -1;
200       while (next != -1)
201       {
202         clear = selected.nextClearBit(next);
203         rlist.add(new int[] { next, clear - 1 });
204         next = selected.nextSetBit(clear);
205       }
206       return rlist;
207     }
208   }
209
210   IntList selected = new IntList();
211
212   /*
213    * list of hidden column [start, end] ranges; the list is maintained in
214    * ascending start column order
215    */
216   Vector<int[]> hiddenColumns;
217
218   /**
219    * Add a column to the selection
220    * 
221    * @param col
222    *          index of column
223    */
224   public void addElement(int col)
225   {
226     selected.add(col);
227   }
228
229   /**
230    * clears column selection
231    */
232   public void clear()
233   {
234     selected.clear();
235   }
236
237   /**
238    * Removes value 'col' from the selection (not the col'th item)
239    * 
240    * @param col
241    *          index of column to be removed
242    */
243   public void removeElement(int col)
244   {
245     selected.remove(col);
246   }
247
248   /**
249    * removes a range of columns from the selection
250    * 
251    * @param start
252    *          int - first column in range to be removed
253    * @param end
254    *          int - last col
255    */
256   public void removeElements(int start, int end)
257   {
258     Integer colInt;
259     for (int i = start; i < end; i++)
260     {
261       colInt = new Integer(i);
262       if (selected.contains(colInt))
263       {
264         selected.remove(colInt);
265       }
266     }
267   }
268
269   /**
270    * Returns a list of selected columns. The list contains no duplicates but is
271    * not necessarily ordered. It also may include columns hidden from the
272    * current view
273    */
274   public List<Integer> getSelected()
275   {
276     return selected.getList();
277   }
278
279   /**
280    * @return list of int arrays containing start and end column position for
281    *         runs of selected columns ordered from right to left.
282    */
283   public List<int[]> getSelectedRanges()
284   {
285     return selected.getRanges();
286   }
287
288   /**
289    * 
290    * @param col
291    *          index to search for in column selection
292    * 
293    * @return true if col is selected
294    */
295   public boolean contains(int col)
296   {
297     return (col > -1) ? selected.isSelected(col) : false;
298   }
299
300   /**
301    * Answers true if no columns are selected, else false
302    */
303   public boolean isEmpty()
304   {
305     return selected == null || selected.isEmpty();
306   }
307
308   /**
309    * rightmost selected column
310    * 
311    * @return rightmost column in alignment that is selected
312    */
313   public int getMax()
314   {
315     if (selected.isEmpty())
316     {
317       return -1;
318     }
319     return selected.getMaxColumn();
320   }
321
322   /**
323    * Leftmost column in selection
324    * 
325    * @return column index of leftmost column in selection
326    */
327   public int getMin()
328   {
329     if (selected.isEmpty())
330     {
331       return 1000000000;
332     }
333     return selected.getMinColumn();
334   }
335
336   /**
337    * propagate shift in alignment columns to column selection
338    * 
339    * @param start
340    *          beginning of edit
341    * @param left
342    *          shift in edit (+ve for removal, or -ve for inserts)
343    */
344   public List<int[]> compensateForEdit(int start, int change)
345   {
346     List<int[]> deletedHiddenColumns = null;
347     selected.compensateForEdits(start, change);
348
349     if (hiddenColumns != null)
350     {
351       deletedHiddenColumns = new ArrayList<int[]>();
352       int hSize = hiddenColumns.size();
353       for (int i = 0; i < hSize; i++)
354       {
355         int[] region = hiddenColumns.elementAt(i);
356         if (region[0] > start && start + change > region[1])
357         {
358           deletedHiddenColumns.add(region);
359
360           hiddenColumns.removeElementAt(i);
361           i--;
362           hSize--;
363           continue;
364         }
365
366         if (region[0] > start)
367         {
368           region[0] -= change;
369           region[1] -= change;
370         }
371
372         if (region[0] < 0)
373         {
374           region[0] = 0;
375         }
376
377       }
378
379       this.revealHiddenColumns(0);
380     }
381
382     return deletedHiddenColumns;
383   }
384
385   /**
386    * propagate shift in alignment columns to column selection special version of
387    * compensateForEdit - allowing for edits within hidden regions
388    * 
389    * @param start
390    *          beginning of edit
391    * @param left
392    *          shift in edit (+ve for removal, or -ve for inserts)
393    */
394   private void compensateForDelEdits(int start, int change)
395   {
396
397     selected.compensateForEdits(start, change);
398
399     if (hiddenColumns != null)
400     {
401       for (int i = 0; i < hiddenColumns.size(); i++)
402       {
403         int[] region = hiddenColumns.elementAt(i);
404         if (region[0] >= start)
405         {
406           region[0] -= change;
407         }
408         if (region[1] >= start)
409         {
410           region[1] -= change;
411         }
412         if (region[1] < region[0])
413         {
414           hiddenColumns.removeElementAt(i--);
415         }
416
417         if (region[0] < 0)
418         {
419           region[0] = 0;
420         }
421         if (region[1] < 0)
422         {
423           region[1] = 0;
424         }
425       }
426     }
427   }
428
429   /**
430    * Adjust hidden column boundaries based on a series of column additions or
431    * deletions in visible regions.
432    * 
433    * @param shiftrecord
434    * @return
435    */
436   public ShiftList compensateForEdits(ShiftList shiftrecord)
437   {
438     if (shiftrecord != null)
439     {
440       final List<int[]> shifts = shiftrecord.getShifts();
441       if (shifts != null && shifts.size() > 0)
442       {
443         int shifted = 0;
444         for (int i = 0, j = shifts.size(); i < j; i++)
445         {
446           int[] sh = shifts.get(i);
447           // compensateForEdit(shifted+sh[0], sh[1]);
448           compensateForDelEdits(shifted + sh[0], sh[1]);
449           shifted -= sh[1];
450         }
451       }
452       return shiftrecord.getInverse();
453     }
454     return null;
455   }
456
457   /**
458    * removes intersection of position,length ranges in deletions from the
459    * start,end regions marked in intervals.
460    * 
461    * @param shifts
462    * @param intervals
463    * @return
464    */
465   private boolean pruneIntervalVector(final List<int[]> shifts,
466           Vector<int[]> intervals)
467   {
468     boolean pruned = false;
469     int i = 0, j = intervals.size() - 1, s = 0, t = shifts.size() - 1;
470     int hr[] = intervals.elementAt(i);
471     int sr[] = shifts.get(s);
472     while (i <= j && s <= t)
473     {
474       boolean trailinghn = hr[1] >= sr[0];
475       if (!trailinghn)
476       {
477         if (i < j)
478         {
479           hr = intervals.elementAt(++i);
480         }
481         else
482         {
483           i++;
484         }
485         continue;
486       }
487       int endshift = sr[0] + sr[1]; // deletion ranges - -ve means an insert
488       if (endshift < hr[0] || endshift < sr[0])
489       { // leadinghc disjoint or not a deletion
490         if (s < t)
491         {
492           sr = shifts.get(++s);
493         }
494         else
495         {
496           s++;
497         }
498         continue;
499       }
500       boolean leadinghn = hr[0] >= sr[0];
501       boolean leadinghc = hr[0] < endshift;
502       boolean trailinghc = hr[1] < endshift;
503       if (leadinghn)
504       {
505         if (trailinghc)
506         { // deleted hidden region.
507           intervals.removeElementAt(i);
508           pruned = true;
509           j--;
510           if (i <= j)
511           {
512             hr = intervals.elementAt(i);
513           }
514           continue;
515         }
516         if (leadinghc)
517         {
518           hr[0] = endshift; // clip c terminal region
519           leadinghn = !leadinghn;
520           pruned = true;
521         }
522       }
523       if (!leadinghn)
524       {
525         if (trailinghc)
526         {
527           if (trailinghn)
528           {
529             hr[1] = sr[0] - 1;
530             pruned = true;
531           }
532         }
533         else
534         {
535           // sr contained in hr
536           if (s < t)
537           {
538             sr = shifts.get(++s);
539           }
540           else
541           {
542             s++;
543           }
544           continue;
545         }
546       }
547     }
548     return pruned; // true if any interval was removed or modified by
549     // operations.
550   }
551
552   /**
553    * remove any hiddenColumns or selected columns and shift remaining based on a
554    * series of position, range deletions.
555    * 
556    * @param deletions
557    */
558   public void pruneDeletions(ShiftList deletions)
559   {
560     if (deletions != null)
561     {
562       final List<int[]> shifts = deletions.getShifts();
563       if (shifts != null && shifts.size() > 0)
564       {
565         // delete any intervals intersecting.
566         if (hiddenColumns != null)
567         {
568           pruneIntervalVector(shifts, hiddenColumns);
569           if (hiddenColumns != null && hiddenColumns.size() == 0)
570           {
571             hiddenColumns = null;
572           }
573         }
574         if (selected != null && selected.size() > 0)
575         {
576           selected.pruneColumnList(shifts);
577           if (selected != null && selected.size() == 0)
578           {
579             selected = null;
580           }
581         }
582         // and shift the rest.
583         this.compensateForEdits(deletions);
584       }
585     }
586   }
587
588   /**
589    * This Method is used to return all the HiddenColumn regions
590    * 
591    * @return empty list or List of hidden column intervals
592    */
593   public List<int[]> getHiddenColumns()
594   {
595     return hiddenColumns == null ? Collections.<int[]> emptyList()
596             : hiddenColumns;
597   }
598
599   /**
600    * Return absolute column index for a visible column index
601    * 
602    * @param column
603    *          int column index in alignment view
604    * @return alignment column index for column
605    */
606   public int adjustForHiddenColumns(int column)
607   {
608     int result = column;
609     if (hiddenColumns != null)
610     {
611       for (int i = 0; i < hiddenColumns.size(); i++)
612       {
613         int[] region = hiddenColumns.elementAt(i);
614         if (result >= region[0])
615         {
616           result += region[1] - region[0] + 1;
617         }
618       }
619     }
620     return result;
621   }
622
623   /**
624    * Use this method to find out where a column will appear in the visible
625    * alignment when hidden columns exist. If the column is not visible, then the
626    * left-most visible column will always be returned.
627    * 
628    * @param hiddenColumn
629    *          int
630    * @return int
631    */
632   public int findColumnPosition(int hiddenColumn)
633   {
634     int result = hiddenColumn;
635     if (hiddenColumns != null)
636     {
637       int index = 0;
638       int[] region;
639       do
640       {
641         region = hiddenColumns.elementAt(index++);
642         if (hiddenColumn > region[1])
643         {
644           result -= region[1] + 1 - region[0];
645         }
646       } while ((hiddenColumn > region[1]) && (index < hiddenColumns.size()));
647       if (hiddenColumn > region[0] && hiddenColumn < region[1])
648       {
649         return region[0] + hiddenColumn - result;
650       }
651     }
652     return result; // return the shifted position after removing hidden columns.
653   }
654
655   /**
656    * Use this method to determine where the next hiddenRegion starts
657    * 
658    * @param hiddenRegion
659    *          index of hidden region (counts from 0)
660    * @return column number in visible view
661    */
662   public int findHiddenRegionPosition(int hiddenRegion)
663   {
664     int result = 0;
665     if (hiddenColumns != null)
666     {
667       int index = 0;
668       int gaps = 0;
669       do
670       {
671         int[] region = hiddenColumns.elementAt(index);
672         if (hiddenRegion == 0)
673         {
674           return region[0];
675         }
676
677         gaps += region[1] + 1 - region[0];
678         result = region[1] + 1;
679         index++;
680       } while (index <= hiddenRegion);
681
682       result -= gaps;
683     }
684
685     return result;
686   }
687
688   /**
689    * THis method returns the rightmost limit of a region of an alignment with
690    * hidden columns. In otherwords, the next hidden column.
691    * 
692    * @param index
693    *          int
694    */
695   public int getHiddenBoundaryRight(int alPos)
696   {
697     if (hiddenColumns != null)
698     {
699       int index = 0;
700       do
701       {
702         int[] region = hiddenColumns.elementAt(index);
703         if (alPos < region[0])
704         {
705           return region[0];
706         }
707
708         index++;
709       } while (index < hiddenColumns.size());
710     }
711
712     return alPos;
713
714   }
715
716   /**
717    * This method returns the leftmost limit of a region of an alignment with
718    * hidden columns. In otherwords, the previous hidden column.
719    * 
720    * @param index
721    *          int
722    */
723   public int getHiddenBoundaryLeft(int alPos)
724   {
725     if (hiddenColumns != null)
726     {
727       int index = hiddenColumns.size() - 1;
728       do
729       {
730         int[] region = hiddenColumns.elementAt(index);
731         if (alPos > region[1])
732         {
733           return region[1];
734         }
735
736         index--;
737       } while (index > -1);
738     }
739
740     return alPos;
741
742   }
743
744   public void hideSelectedColumns()
745   {
746     synchronized (selected)
747     {
748       for (int[] selregions : selected.getRanges())
749       {
750         hideColumns(selregions[0], selregions[1]);
751       }
752       selected.clear();
753     }
754
755   }
756
757   /**
758    * Adds the specified column range to the hidden columns
759    * 
760    * @param start
761    * @param end
762    */
763   public void hideColumns(int start, int end)
764   {
765     if (hiddenColumns == null)
766     {
767       hiddenColumns = new Vector<int[]>();
768     }
769
770     /*
771      * traverse existing hidden ranges and insert / amend / append as
772      * appropriate
773      */
774     for (int i = 0; i < hiddenColumns.size(); i++)
775     {
776       int[] region = hiddenColumns.elementAt(i);
777
778       if (end < region[0] - 1)
779       {
780         /*
781          * insert discontiguous preceding range
782          */
783         hiddenColumns.insertElementAt(new int[] { start, end }, i);
784         return;
785       }
786
787       if (end <= region[1])
788       {
789         /*
790          * new range overlaps existing, or is contiguous preceding it - adjust
791          * start column
792          */
793         region[0] = Math.min(region[0], start);
794         return;
795       }
796
797       if (start <= region[1] + 1)
798       {
799         /*
800          * new range overlaps existing, or is contiguous following it - adjust
801          * start and end columns
802          */
803         region[0] = Math.min(region[0], start);
804         region[1] = Math.max(region[1], end);
805         return;
806       }
807     }
808
809     /*
810      * remaining case is that the new range follows everything else
811      */
812     hiddenColumns.addElement(new int[] { start, end });
813   }
814
815   /**
816    * Hides the specified column and any adjacent selected columns
817    * 
818    * @param res
819    *          int
820    */
821   public void hideColumns(int col)
822   {
823     /*
824      * deselect column (whether selected or not!)
825      */
826     removeElement(col);
827
828     /*
829      * find adjacent selected columns
830      */
831     int min = col - 1, max = col + 1;
832     while (contains(min))
833     {
834       removeElement(min);
835       min--;
836     }
837
838     while (contains(max))
839     {
840       removeElement(max);
841       max++;
842     }
843
844     /*
845      * min, max are now the closest unselected columns
846      */
847     min++;
848     max--;
849     if (min > max)
850     {
851       min = max;
852     }
853
854     hideColumns(min, max);
855   }
856
857   /**
858    * Unhides, and adds to the selection list, all hidden columns
859    */
860   public void revealAllHiddenColumns()
861   {
862     if (hiddenColumns != null)
863     {
864       for (int i = 0; i < hiddenColumns.size(); i++)
865       {
866         int[] region = hiddenColumns.elementAt(i);
867         for (int j = region[0]; j < region[1] + 1; j++)
868         {
869           addElement(j);
870         }
871       }
872     }
873
874     hiddenColumns = null;
875   }
876
877   /**
878    * Reveals, and marks as selected, the hidden column range with the given
879    * start column
880    * 
881    * @param start
882    */
883   public void revealHiddenColumns(int start)
884   {
885     for (int i = 0; i < hiddenColumns.size(); i++)
886     {
887       int[] region = hiddenColumns.elementAt(i);
888       if (start == region[0])
889       {
890         for (int j = region[0]; j < region[1] + 1; j++)
891         {
892           addElement(j);
893         }
894
895         hiddenColumns.removeElement(region);
896         break;
897       }
898     }
899     if (hiddenColumns.size() == 0)
900     {
901       hiddenColumns = null;
902     }
903   }
904
905   public boolean isVisible(int column)
906   {
907     if (hiddenColumns != null)
908     {
909       for (int[] region : hiddenColumns)
910       {
911         if (column >= region[0] && column <= region[1])
912         {
913           return false;
914         }
915       }
916     }
917
918     return true;
919   }
920
921   /**
922    * Copy constructor
923    * 
924    * @param copy
925    */
926   public ColumnSelection(ColumnSelection copy)
927   {
928     if (copy != null)
929     {
930       if (copy.selected != null)
931       {
932         selected = new IntList();
933         for (int i = 0, j = copy.selected.size(); i < j; i++)
934         {
935           selected.add(copy.selected.elementAt(i));
936         }
937       }
938       if (copy.hiddenColumns != null)
939       {
940         hiddenColumns = new Vector<int[]>(copy.hiddenColumns.size());
941         for (int i = 0, j = copy.hiddenColumns.size(); i < j; i++)
942         {
943           int[] rh, cp;
944           rh = copy.hiddenColumns.elementAt(i);
945           if (rh != null)
946           {
947             cp = new int[rh.length];
948             System.arraycopy(rh, 0, cp, 0, rh.length);
949             hiddenColumns.addElement(cp);
950           }
951         }
952       }
953     }
954   }
955
956   /**
957    * ColumnSelection
958    */
959   public ColumnSelection()
960   {
961   }
962
963   public String[] getVisibleSequenceStrings(int start, int end,
964           SequenceI[] seqs)
965   {
966     int i, iSize = seqs.length;
967     String selection[] = new String[iSize];
968     if (hiddenColumns != null && hiddenColumns.size() > 0)
969     {
970       for (i = 0; i < iSize; i++)
971       {
972         StringBuffer visibleSeq = new StringBuffer();
973         List<int[]> regions = getHiddenColumns();
974
975         int blockStart = start, blockEnd = end;
976         int[] region;
977         int hideStart, hideEnd;
978
979         for (int j = 0; j < regions.size(); j++)
980         {
981           region = regions.get(j);
982           hideStart = region[0];
983           hideEnd = region[1];
984
985           if (hideStart < start)
986           {
987             continue;
988           }
989
990           blockStart = Math.min(blockStart, hideEnd + 1);
991           blockEnd = Math.min(blockEnd, hideStart);
992
993           if (blockStart > blockEnd)
994           {
995             break;
996           }
997
998           visibleSeq.append(seqs[i].getSequence(blockStart, blockEnd));
999
1000           blockStart = hideEnd + 1;
1001           blockEnd = end;
1002         }
1003
1004         if (end > blockStart)
1005         {
1006           visibleSeq.append(seqs[i].getSequence(blockStart, end));
1007         }
1008
1009         selection[i] = visibleSeq.toString();
1010       }
1011     }
1012     else
1013     {
1014       for (i = 0; i < iSize; i++)
1015       {
1016         selection[i] = seqs[i].getSequenceAsString(start, end);
1017       }
1018     }
1019
1020     return selection;
1021   }
1022
1023   /**
1024    * return all visible segments between the given start and end boundaries
1025    * 
1026    * @param start
1027    *          (first column inclusive from 0)
1028    * @param end
1029    *          (last column - not inclusive)
1030    * @return int[] {i_start, i_end, ..} where intervals lie in
1031    *         start<=i_start<=i_end<end
1032    */
1033   public int[] getVisibleContigs(int start, int end)
1034   {
1035     if (hiddenColumns != null && hiddenColumns.size() > 0)
1036     {
1037       List<int[]> visiblecontigs = new ArrayList<int[]>();
1038       List<int[]> regions = getHiddenColumns();
1039
1040       int vstart = start;
1041       int[] region;
1042       int hideStart, hideEnd;
1043
1044       for (int j = 0; vstart < end && j < regions.size(); j++)
1045       {
1046         region = regions.get(j);
1047         hideStart = region[0];
1048         hideEnd = region[1];
1049
1050         if (hideEnd < vstart)
1051         {
1052           continue;
1053         }
1054         if (hideStart > vstart)
1055         {
1056           visiblecontigs.add(new int[] { vstart, hideStart - 1 });
1057         }
1058         vstart = hideEnd + 1;
1059       }
1060
1061       if (vstart < end)
1062       {
1063         visiblecontigs.add(new int[] { vstart, end - 1 });
1064       }
1065       int[] vcontigs = new int[visiblecontigs.size() * 2];
1066       for (int i = 0, j = visiblecontigs.size(); i < j; i++)
1067       {
1068         int[] vc = visiblecontigs.get(i);
1069         visiblecontigs.set(i, null);
1070         vcontigs[i * 2] = vc[0];
1071         vcontigs[i * 2 + 1] = vc[1];
1072       }
1073       visiblecontigs.clear();
1074       return vcontigs;
1075     }
1076     else
1077     {
1078       return new int[] { start, end - 1 };
1079     }
1080   }
1081
1082   /**
1083    * Locate the first and last position visible for this sequence. if seq isn't
1084    * visible then return the position of the left and right of the hidden
1085    * boundary region
1086    * 
1087    * @param seq
1088    * @return int[] { visible start, visible end, first seqpos, last seqpos }
1089    */
1090   public int[] locateVisibleBoundsOfSequence(SequenceI seq)
1091   {
1092     int fpos=seq.getStart(),lpos= seq.getEnd();
1093     int start = 0;
1094     int end = seq.getLength();
1095     
1096     if (hiddenColumns == null || hiddenColumns.size() == 0)
1097     {
1098       return new int[] { seq.findIndex(fpos), seq.findIndex(lpos), fpos,
1099           lpos };
1100     }
1101
1102     // Simply walk along the sequence whilst watching for hidden column
1103     // boundaries
1104     List<int[]> regions = getHiddenColumns();
1105     int spos = fpos, lastvispos = -1, rcount = 0, hideStart = seq
1106             .getLength(), hideEnd = -1;
1107     int visPrev = 0, visNext = 0, base = 0;
1108     boolean foundStart = false;
1109     for (int p = 0, pLen = seq.getLength(); spos <= seq.getEnd()
1110             && p < pLen; p++)
1111     {
1112       if (!Comparison.isGap(seq.getCharAt(p)))
1113       {
1114         // update hidden region start/end
1115         while (hideEnd < p && rcount < regions.size())
1116         {
1117           int[] region = regions.get(rcount++);
1118           visNext += region[1] + 1 - region[0];
1119           visPrev = visNext-1;
1120           hideStart = region[0];
1121           hideEnd = region[1];
1122         }
1123         if (hideEnd < p)
1124         {
1125           hideStart = seq.getLength();
1126         }
1127         // update visible boundary for sequence
1128         if (p < hideStart)
1129         {
1130           if (!foundStart)
1131           {
1132             fpos = spos;
1133             start = p;
1134             foundStart = true;
1135           }
1136           lastvispos = p;
1137           lpos = spos;
1138         }
1139         // look for next sequence position
1140         spos++;
1141       }
1142     }
1143     if (foundStart)
1144     {
1145       return new int[] { findColumnPosition(start),
1146           findColumnPosition(lastvispos), fpos, lpos };
1147     }
1148     // otherwise, sequence was completely hidden
1149     return new int[] { visPrev, visNext, 0, 0 };
1150   }
1151
1152   /**
1153    * delete any columns in alignmentAnnotation that are hidden (including
1154    * sequence associated annotation).
1155    * 
1156    * @param alignmentAnnotation
1157    */
1158   public void makeVisibleAnnotation(AlignmentAnnotation alignmentAnnotation)
1159   {
1160     makeVisibleAnnotation(-1, -1, alignmentAnnotation);
1161   }
1162
1163   /**
1164    * delete any columns in alignmentAnnotation that are hidden (including
1165    * sequence associated annotation).
1166    * 
1167    * @param start
1168    *          remove any annotation to the right of this column
1169    * @param end
1170    *          remove any annotation to the left of this column
1171    * @param alignmentAnnotation
1172    *          the annotation to operate on
1173    */
1174   public void makeVisibleAnnotation(int start, int end,
1175           AlignmentAnnotation alignmentAnnotation)
1176   {
1177     if (alignmentAnnotation.annotations == null)
1178     {
1179       return;
1180     }
1181     if (start == end && end == -1)
1182     {
1183       start = 0;
1184       end = alignmentAnnotation.annotations.length;
1185     }
1186     if (hiddenColumns != null && hiddenColumns.size() > 0)
1187     {
1188       // then mangle the alignmentAnnotation annotation array
1189       Vector<Annotation[]> annels = new Vector<Annotation[]>();
1190       Annotation[] els = null;
1191       List<int[]> regions = getHiddenColumns();
1192       int blockStart = start, blockEnd = end;
1193       int[] region;
1194       int hideStart, hideEnd, w = 0;
1195
1196       for (int j = 0; j < regions.size(); j++)
1197       {
1198         region = regions.get(j);
1199         hideStart = region[0];
1200         hideEnd = region[1];
1201
1202         if (hideStart < start)
1203         {
1204           continue;
1205         }
1206
1207         blockStart = Math.min(blockStart, hideEnd + 1);
1208         blockEnd = Math.min(blockEnd, hideStart);
1209
1210         if (blockStart > blockEnd)
1211         {
1212           break;
1213         }
1214
1215         annels.addElement(els = new Annotation[blockEnd - blockStart]);
1216         System.arraycopy(alignmentAnnotation.annotations, blockStart, els,
1217                 0, els.length);
1218         w += els.length;
1219         blockStart = hideEnd + 1;
1220         blockEnd = end;
1221       }
1222
1223       if (end > blockStart)
1224       {
1225         annels.addElement(els = new Annotation[end - blockStart + 1]);
1226         if ((els.length + blockStart) <= alignmentAnnotation.annotations.length)
1227         {
1228           // copy just the visible segment of the annotation row
1229           System.arraycopy(alignmentAnnotation.annotations, blockStart,
1230                   els, 0, els.length);
1231         }
1232         else
1233         {
1234           // copy to the end of the annotation row
1235           System.arraycopy(alignmentAnnotation.annotations, blockStart,
1236                   els, 0,
1237                   (alignmentAnnotation.annotations.length - blockStart));
1238         }
1239         w += els.length;
1240       }
1241       if (w == 0)
1242       {
1243         return;
1244       }
1245
1246       alignmentAnnotation.annotations = new Annotation[w];
1247       w = 0;
1248
1249       for (Annotation[] chnk : annels)
1250       {
1251         System.arraycopy(chnk, 0, alignmentAnnotation.annotations, w,
1252                 chnk.length);
1253         w += chnk.length;
1254       }
1255     }
1256     else
1257     {
1258       alignmentAnnotation.restrict(start, end);
1259     }
1260   }
1261
1262   /**
1263    * Invert the column selection from first to end-1. leaves hiddenColumns
1264    * untouched (and unselected)
1265    * 
1266    * @param first
1267    * @param end
1268    */
1269   public void invertColumnSelection(int first, int width)
1270   {
1271     boolean hasHidden = hiddenColumns != null && hiddenColumns.size() > 0;
1272     for (int i = first; i < width; i++)
1273     {
1274       if (contains(i))
1275       {
1276         removeElement(i);
1277       }
1278       else
1279       {
1280         if (!hasHidden || isVisible(i))
1281         {
1282           addElement(i);
1283         }
1284       }
1285     }
1286   }
1287
1288   /**
1289    * add in any unselected columns from the given column selection, excluding
1290    * any that are hidden.
1291    * 
1292    * @param colsel
1293    */
1294   public void addElementsFrom(ColumnSelection colsel)
1295   {
1296     if (colsel != null && !colsel.isEmpty())
1297     {
1298       for (Integer col : colsel.getSelected())
1299       {
1300         if (hiddenColumns != null && isVisible(col.intValue()))
1301         {
1302           selected.add(col);
1303         }
1304       }
1305     }
1306   }
1307
1308   /**
1309    * set the selected columns the given column selection, excluding any columns
1310    * that are hidden.
1311    * 
1312    * @param colsel
1313    */
1314   public void setElementsFrom(ColumnSelection colsel)
1315   {
1316     selected = new IntList();
1317     if (colsel.selected != null && colsel.selected.size() > 0)
1318     {
1319       if (hiddenColumns != null && hiddenColumns.size() > 0)
1320       {
1321         // only select visible columns in this columns selection
1322         addElementsFrom(colsel);
1323       }
1324       else
1325       {
1326         // add everything regardless
1327         for (Integer col : colsel.getSelected())
1328         {
1329           addElement(col);
1330         }
1331       }
1332     }
1333   }
1334
1335   /**
1336    * Add gaps into the sequences aligned to profileseq under the given
1337    * AlignmentView
1338    * 
1339    * @param profileseq
1340    * @param al
1341    *          - alignment to have gaps inserted into it
1342    * @param input
1343    *          - alignment view where sequence corresponding to profileseq is
1344    *          first entry
1345    * @return new Column selection for new alignment view, with insertions into
1346    *         profileseq marked as hidden.
1347    */
1348   public static ColumnSelection propagateInsertions(SequenceI profileseq,
1349           AlignmentI al, AlignmentView input)
1350   {
1351     int profsqpos = 0;
1352
1353     // return propagateInsertions(profileseq, al, )
1354     char gc = al.getGapCharacter();
1355     Object[] alandcolsel = input.getAlignmentAndColumnSelection(gc);
1356     ColumnSelection nview = (ColumnSelection) alandcolsel[1];
1357     SequenceI origseq = ((SequenceI[]) alandcolsel[0])[profsqpos];
1358     nview.propagateInsertions(profileseq, al, origseq);
1359     return nview;
1360   }
1361
1362   /**
1363    * 
1364    * @param profileseq
1365    *          - sequence in al which corresponds to origseq
1366    * @param al
1367    *          - alignment which is to have gaps inserted into it
1368    * @param origseq
1369    *          - sequence corresponding to profileseq which defines gap map for
1370    *          modifying al
1371    */
1372   public void propagateInsertions(SequenceI profileseq, AlignmentI al,
1373           SequenceI origseq)
1374   {
1375     char gc = al.getGapCharacter();
1376     // recover mapping between sequence's non-gap positions and positions
1377     // mapping to view.
1378     pruneDeletions(ShiftList.parseMap(origseq.gapMap()));
1379     int[] viscontigs = getVisibleContigs(0, profileseq.getLength());
1380     int spos = 0;
1381     int offset = 0;
1382     // input.pruneDeletions(ShiftList.parseMap(((SequenceI[])
1383     // alandcolsel[0])[0].gapMap()))
1384     // add profile to visible contigs
1385     for (int v = 0; v < viscontigs.length; v += 2)
1386     {
1387       if (viscontigs[v] > spos)
1388       {
1389         StringBuffer sb = new StringBuffer();
1390         for (int s = 0, ns = viscontigs[v] - spos; s < ns; s++)
1391         {
1392           sb.append(gc);
1393         }
1394         for (int s = 0, ns = al.getHeight(); s < ns; s++)
1395         {
1396           SequenceI sqobj = al.getSequenceAt(s);
1397           if (sqobj != profileseq)
1398           {
1399             String sq = al.getSequenceAt(s).getSequenceAsString();
1400             if (sq.length() <= spos + offset)
1401             {
1402               // pad sequence
1403               int diff = spos + offset - sq.length() - 1;
1404               if (diff > 0)
1405               {
1406                 // pad gaps
1407                 sq = sq + sb;
1408                 while ((diff = spos + offset - sq.length() - 1) > 0)
1409                 {
1410                   // sq = sq
1411                   // + ((diff >= sb.length()) ? sb.toString() : sb
1412                   // .substring(0, diff));
1413                   if (diff >= sb.length())
1414                   {
1415                     sq += sb.toString();
1416                   }
1417                   else
1418                   {
1419                     char[] buf = new char[diff];
1420                     sb.getChars(0, diff, buf, 0);
1421                     sq += buf.toString();
1422                   }
1423                 }
1424               }
1425               sq += sb.toString();
1426             }
1427             else
1428             {
1429               al.getSequenceAt(s).setSequence(
1430                       sq.substring(0, spos + offset) + sb.toString()
1431                               + sq.substring(spos + offset));
1432             }
1433           }
1434         }
1435         // offset+=sb.length();
1436       }
1437       spos = viscontigs[v + 1] + 1;
1438     }
1439     if ((offset + spos) < profileseq.getLength())
1440     {
1441       // pad the final region with gaps.
1442       StringBuffer sb = new StringBuffer();
1443       for (int s = 0, ns = profileseq.getLength() - spos - offset; s < ns; s++)
1444       {
1445         sb.append(gc);
1446       }
1447       for (int s = 0, ns = al.getHeight(); s < ns; s++)
1448       {
1449         SequenceI sqobj = al.getSequenceAt(s);
1450         if (sqobj == profileseq)
1451         {
1452           continue;
1453         }
1454         String sq = sqobj.getSequenceAsString();
1455         // pad sequence
1456         int diff = origseq.getLength() - sq.length();
1457         while (diff > 0)
1458         {
1459           // sq = sq
1460           // + ((diff >= sb.length()) ? sb.toString() : sb
1461           // .substring(0, diff));
1462           if (diff >= sb.length())
1463           {
1464             sq += sb.toString();
1465           }
1466           else
1467           {
1468             char[] buf = new char[diff];
1469             sb.getChars(0, diff, buf, 0);
1470             sq += buf.toString();
1471           }
1472           diff = origseq.getLength() - sq.length();
1473         }
1474       }
1475     }
1476   }
1477
1478   /**
1479    * 
1480    * @return true if there are columns marked
1481    */
1482   public boolean hasSelectedColumns()
1483   {
1484     return (selected != null && selected.size() > 0);
1485   }
1486
1487   /**
1488    * 
1489    * @return true if there are columns hidden
1490    */
1491   public boolean hasHiddenColumns()
1492   {
1493     return hiddenColumns != null && hiddenColumns.size() > 0;
1494   }
1495
1496   /**
1497    * 
1498    * @return true if there are more than one set of columns hidden
1499    */
1500   public boolean hasManyHiddenColumns()
1501   {
1502     return hiddenColumns != null && hiddenColumns.size() > 1;
1503   }
1504
1505   /**
1506    * mark the columns corresponding to gap characters as hidden in the column
1507    * selection
1508    * 
1509    * @param sr
1510    */
1511   public void hideInsertionsFor(SequenceI sr)
1512   {
1513     List<int[]> inserts = sr.getInsertions();
1514     for (int[] r : inserts)
1515     {
1516       hideColumns(r[0], r[1]);
1517     }
1518   }
1519
1520   public boolean filterAnnotations(Annotation[] annotations,
1521           AnnotationFilterParameter filterParams)
1522   {
1523     // JBPNote - this method needs to be refactored to become independent of
1524     // viewmodel package
1525     this.revealAllHiddenColumns();
1526     this.clear();
1527     int count = 0;
1528     do
1529     {
1530       if (annotations[count] != null)
1531       {
1532
1533         boolean itemMatched = false;
1534
1535         if (filterParams.getThresholdType() == AnnotationFilterParameter.ThresholdType.ABOVE_THRESHOLD
1536                 && annotations[count].value >= filterParams
1537                         .getThresholdValue())
1538         {
1539           itemMatched = true;
1540         }
1541         if (filterParams.getThresholdType() == AnnotationFilterParameter.ThresholdType.BELOW_THRESHOLD
1542                 && annotations[count].value <= filterParams
1543                         .getThresholdValue())
1544         {
1545           itemMatched = true;
1546         }
1547
1548         if (filterParams.isFilterAlphaHelix()
1549                 && annotations[count].secondaryStructure == 'H')
1550         {
1551           itemMatched = true;
1552         }
1553
1554         if (filterParams.isFilterBetaSheet()
1555                 && annotations[count].secondaryStructure == 'E')
1556         {
1557           itemMatched = true;
1558         }
1559
1560         if (filterParams.isFilterTurn()
1561                 && annotations[count].secondaryStructure == 'S')
1562         {
1563           itemMatched = true;
1564         }
1565
1566         String regexSearchString = filterParams.getRegexString();
1567         if (regexSearchString != null
1568                 && !filterParams.getRegexSearchFields().isEmpty())
1569         {
1570           List<SearchableAnnotationField> fields = filterParams
1571                   .getRegexSearchFields();
1572           try
1573           {
1574             if (fields.contains(SearchableAnnotationField.DISPLAY_STRING)
1575                     && annotations[count].displayCharacter
1576                             .matches(regexSearchString))
1577             {
1578               itemMatched = true;
1579             }
1580           } catch (java.util.regex.PatternSyntaxException pse)
1581           {
1582             if (annotations[count].displayCharacter
1583                     .equals(regexSearchString))
1584             {
1585               itemMatched = true;
1586             }
1587           }
1588           if (fields.contains(SearchableAnnotationField.DESCRIPTION)
1589                   && annotations[count].description != null
1590                   && annotations[count].description
1591                           .matches(regexSearchString))
1592           {
1593             itemMatched = true;
1594           }
1595         }
1596
1597         if (itemMatched)
1598         {
1599           this.addElement(count);
1600         }
1601       }
1602       count++;
1603     } while (count < annotations.length);
1604     return false;
1605   }
1606
1607 }