JAL-192 need visible and actual sequence limits for scale
[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, and the corresponding alignment column indices for the
1086    * extent of the sequence
1087    * 
1088    * @param seq
1089    * @return int[] { visible start, visible end, first seqpos, last seqpos,
1090    *         alignment index for seq start, alignment index for seq end }
1091    */
1092   public int[] locateVisibleBoundsOfSequence(SequenceI seq)
1093   {
1094     int fpos=seq.getStart(),lpos= seq.getEnd();
1095     int start = 0;
1096     
1097     if (hiddenColumns == null || hiddenColumns.size() == 0)
1098     {
1099       int ifpos = seq.findIndex(fpos) - 1, ilpos = seq.findIndex(lpos) - 1;
1100       return new int[] { ifpos, ilpos, fpos, lpos, ifpos, ilpos };
1101     }
1102
1103     // Simply walk along the sequence whilst watching for hidden column
1104     // boundaries
1105     List<int[]> regions = getHiddenColumns();
1106     int spos = fpos, lastvispos = -1, rcount = 0, hideStart = seq
1107             .getLength(), hideEnd = -1;
1108     int visPrev = 0, visNext = 0, firstP = -1, lastP = -1;
1109     boolean foundStart = false;
1110     for (int p = 0, pLen = seq.getLength(); spos <= seq.getEnd()
1111             && p < pLen; p++)
1112     {
1113       if (!Comparison.isGap(seq.getCharAt(p)))
1114       {
1115         // keep track of first/last column
1116         // containing sequence data regardless of visibility
1117         if (firstP == -1)
1118         {
1119           firstP = p;
1120         }
1121         lastP = p;
1122         // update hidden region start/end
1123         while (hideEnd < p && rcount < regions.size())
1124         {
1125           int[] region = regions.get(rcount++);
1126           visPrev = visNext;
1127           visNext += region[0] - visPrev;
1128           hideStart = region[0];
1129           hideEnd = region[1];
1130         }
1131         if (hideEnd < p)
1132         {
1133           hideStart = seq.getLength();
1134         }
1135         // update visible boundary for sequence
1136         if (p < hideStart)
1137         {
1138           if (!foundStart)
1139           {
1140             fpos = spos;
1141             start = p;
1142             foundStart = true;
1143           }
1144           lastvispos = p;
1145           lpos = spos;
1146         }
1147         // look for next sequence position
1148         spos++;
1149       }
1150     }
1151     if (foundStart)
1152     {
1153       return new int[] { findColumnPosition(start),
1154           findColumnPosition(lastvispos), fpos, lpos, firstP, lastP };
1155     }
1156     // otherwise, sequence was completely hidden
1157     return new int[] { visPrev, visNext, 0, 0, firstP, lastP };
1158   }
1159
1160   /**
1161    * delete any columns in alignmentAnnotation that are hidden (including
1162    * sequence associated annotation).
1163    * 
1164    * @param alignmentAnnotation
1165    */
1166   public void makeVisibleAnnotation(AlignmentAnnotation alignmentAnnotation)
1167   {
1168     makeVisibleAnnotation(-1, -1, alignmentAnnotation);
1169   }
1170
1171   /**
1172    * delete any columns in alignmentAnnotation that are hidden (including
1173    * sequence associated annotation).
1174    * 
1175    * @param start
1176    *          remove any annotation to the right of this column
1177    * @param end
1178    *          remove any annotation to the left of this column
1179    * @param alignmentAnnotation
1180    *          the annotation to operate on
1181    */
1182   public void makeVisibleAnnotation(int start, int end,
1183           AlignmentAnnotation alignmentAnnotation)
1184   {
1185     if (alignmentAnnotation.annotations == null)
1186     {
1187       return;
1188     }
1189     if (start == end && end == -1)
1190     {
1191       start = 0;
1192       end = alignmentAnnotation.annotations.length;
1193     }
1194     if (hiddenColumns != null && hiddenColumns.size() > 0)
1195     {
1196       // then mangle the alignmentAnnotation annotation array
1197       Vector<Annotation[]> annels = new Vector<Annotation[]>();
1198       Annotation[] els = null;
1199       List<int[]> regions = getHiddenColumns();
1200       int blockStart = start, blockEnd = end;
1201       int[] region;
1202       int hideStart, hideEnd, w = 0;
1203
1204       for (int j = 0; j < regions.size(); j++)
1205       {
1206         region = regions.get(j);
1207         hideStart = region[0];
1208         hideEnd = region[1];
1209
1210         if (hideStart < start)
1211         {
1212           continue;
1213         }
1214
1215         blockStart = Math.min(blockStart, hideEnd + 1);
1216         blockEnd = Math.min(blockEnd, hideStart);
1217
1218         if (blockStart > blockEnd)
1219         {
1220           break;
1221         }
1222
1223         annels.addElement(els = new Annotation[blockEnd - blockStart]);
1224         System.arraycopy(alignmentAnnotation.annotations, blockStart, els,
1225                 0, els.length);
1226         w += els.length;
1227         blockStart = hideEnd + 1;
1228         blockEnd = end;
1229       }
1230
1231       if (end > blockStart)
1232       {
1233         annels.addElement(els = new Annotation[end - blockStart + 1]);
1234         if ((els.length + blockStart) <= alignmentAnnotation.annotations.length)
1235         {
1236           // copy just the visible segment of the annotation row
1237           System.arraycopy(alignmentAnnotation.annotations, blockStart,
1238                   els, 0, els.length);
1239         }
1240         else
1241         {
1242           // copy to the end of the annotation row
1243           System.arraycopy(alignmentAnnotation.annotations, blockStart,
1244                   els, 0,
1245                   (alignmentAnnotation.annotations.length - blockStart));
1246         }
1247         w += els.length;
1248       }
1249       if (w == 0)
1250       {
1251         return;
1252       }
1253
1254       alignmentAnnotation.annotations = new Annotation[w];
1255       w = 0;
1256
1257       for (Annotation[] chnk : annels)
1258       {
1259         System.arraycopy(chnk, 0, alignmentAnnotation.annotations, w,
1260                 chnk.length);
1261         w += chnk.length;
1262       }
1263     }
1264     else
1265     {
1266       alignmentAnnotation.restrict(start, end);
1267     }
1268   }
1269
1270   /**
1271    * Invert the column selection from first to end-1. leaves hiddenColumns
1272    * untouched (and unselected)
1273    * 
1274    * @param first
1275    * @param end
1276    */
1277   public void invertColumnSelection(int first, int width)
1278   {
1279     boolean hasHidden = hiddenColumns != null && hiddenColumns.size() > 0;
1280     for (int i = first; i < width; i++)
1281     {
1282       if (contains(i))
1283       {
1284         removeElement(i);
1285       }
1286       else
1287       {
1288         if (!hasHidden || isVisible(i))
1289         {
1290           addElement(i);
1291         }
1292       }
1293     }
1294   }
1295
1296   /**
1297    * add in any unselected columns from the given column selection, excluding
1298    * any that are hidden.
1299    * 
1300    * @param colsel
1301    */
1302   public void addElementsFrom(ColumnSelection colsel)
1303   {
1304     if (colsel != null && !colsel.isEmpty())
1305     {
1306       for (Integer col : colsel.getSelected())
1307       {
1308         if (hiddenColumns != null && isVisible(col.intValue()))
1309         {
1310           selected.add(col);
1311         }
1312       }
1313     }
1314   }
1315
1316   /**
1317    * set the selected columns the given column selection, excluding any columns
1318    * that are hidden.
1319    * 
1320    * @param colsel
1321    */
1322   public void setElementsFrom(ColumnSelection colsel)
1323   {
1324     selected = new IntList();
1325     if (colsel.selected != null && colsel.selected.size() > 0)
1326     {
1327       if (hiddenColumns != null && hiddenColumns.size() > 0)
1328       {
1329         // only select visible columns in this columns selection
1330         addElementsFrom(colsel);
1331       }
1332       else
1333       {
1334         // add everything regardless
1335         for (Integer col : colsel.getSelected())
1336         {
1337           addElement(col);
1338         }
1339       }
1340     }
1341   }
1342
1343   /**
1344    * Add gaps into the sequences aligned to profileseq under the given
1345    * AlignmentView
1346    * 
1347    * @param profileseq
1348    * @param al
1349    *          - alignment to have gaps inserted into it
1350    * @param input
1351    *          - alignment view where sequence corresponding to profileseq is
1352    *          first entry
1353    * @return new Column selection for new alignment view, with insertions into
1354    *         profileseq marked as hidden.
1355    */
1356   public static ColumnSelection propagateInsertions(SequenceI profileseq,
1357           AlignmentI al, AlignmentView input)
1358   {
1359     int profsqpos = 0;
1360
1361     // return propagateInsertions(profileseq, al, )
1362     char gc = al.getGapCharacter();
1363     Object[] alandcolsel = input.getAlignmentAndColumnSelection(gc);
1364     ColumnSelection nview = (ColumnSelection) alandcolsel[1];
1365     SequenceI origseq = ((SequenceI[]) alandcolsel[0])[profsqpos];
1366     nview.propagateInsertions(profileseq, al, origseq);
1367     return nview;
1368   }
1369
1370   /**
1371    * 
1372    * @param profileseq
1373    *          - sequence in al which corresponds to origseq
1374    * @param al
1375    *          - alignment which is to have gaps inserted into it
1376    * @param origseq
1377    *          - sequence corresponding to profileseq which defines gap map for
1378    *          modifying al
1379    */
1380   public void propagateInsertions(SequenceI profileseq, AlignmentI al,
1381           SequenceI origseq)
1382   {
1383     char gc = al.getGapCharacter();
1384     // recover mapping between sequence's non-gap positions and positions
1385     // mapping to view.
1386     pruneDeletions(ShiftList.parseMap(origseq.gapMap()));
1387     int[] viscontigs = getVisibleContigs(0, profileseq.getLength());
1388     int spos = 0;
1389     int offset = 0;
1390     // input.pruneDeletions(ShiftList.parseMap(((SequenceI[])
1391     // alandcolsel[0])[0].gapMap()))
1392     // add profile to visible contigs
1393     for (int v = 0; v < viscontigs.length; v += 2)
1394     {
1395       if (viscontigs[v] > spos)
1396       {
1397         StringBuffer sb = new StringBuffer();
1398         for (int s = 0, ns = viscontigs[v] - spos; s < ns; s++)
1399         {
1400           sb.append(gc);
1401         }
1402         for (int s = 0, ns = al.getHeight(); s < ns; s++)
1403         {
1404           SequenceI sqobj = al.getSequenceAt(s);
1405           if (sqobj != profileseq)
1406           {
1407             String sq = al.getSequenceAt(s).getSequenceAsString();
1408             if (sq.length() <= spos + offset)
1409             {
1410               // pad sequence
1411               int diff = spos + offset - sq.length() - 1;
1412               if (diff > 0)
1413               {
1414                 // pad gaps
1415                 sq = sq + sb;
1416                 while ((diff = spos + offset - sq.length() - 1) > 0)
1417                 {
1418                   // sq = sq
1419                   // + ((diff >= sb.length()) ? sb.toString() : sb
1420                   // .substring(0, diff));
1421                   if (diff >= sb.length())
1422                   {
1423                     sq += sb.toString();
1424                   }
1425                   else
1426                   {
1427                     char[] buf = new char[diff];
1428                     sb.getChars(0, diff, buf, 0);
1429                     sq += buf.toString();
1430                   }
1431                 }
1432               }
1433               sq += sb.toString();
1434             }
1435             else
1436             {
1437               al.getSequenceAt(s).setSequence(
1438                       sq.substring(0, spos + offset) + sb.toString()
1439                               + sq.substring(spos + offset));
1440             }
1441           }
1442         }
1443         // offset+=sb.length();
1444       }
1445       spos = viscontigs[v + 1] + 1;
1446     }
1447     if ((offset + spos) < profileseq.getLength())
1448     {
1449       // pad the final region with gaps.
1450       StringBuffer sb = new StringBuffer();
1451       for (int s = 0, ns = profileseq.getLength() - spos - offset; s < ns; s++)
1452       {
1453         sb.append(gc);
1454       }
1455       for (int s = 0, ns = al.getHeight(); s < ns; s++)
1456       {
1457         SequenceI sqobj = al.getSequenceAt(s);
1458         if (sqobj == profileseq)
1459         {
1460           continue;
1461         }
1462         String sq = sqobj.getSequenceAsString();
1463         // pad sequence
1464         int diff = origseq.getLength() - sq.length();
1465         while (diff > 0)
1466         {
1467           // sq = sq
1468           // + ((diff >= sb.length()) ? sb.toString() : sb
1469           // .substring(0, diff));
1470           if (diff >= sb.length())
1471           {
1472             sq += sb.toString();
1473           }
1474           else
1475           {
1476             char[] buf = new char[diff];
1477             sb.getChars(0, diff, buf, 0);
1478             sq += buf.toString();
1479           }
1480           diff = origseq.getLength() - sq.length();
1481         }
1482       }
1483     }
1484   }
1485
1486   /**
1487    * 
1488    * @return true if there are columns marked
1489    */
1490   public boolean hasSelectedColumns()
1491   {
1492     return (selected != null && selected.size() > 0);
1493   }
1494
1495   /**
1496    * 
1497    * @return true if there are columns hidden
1498    */
1499   public boolean hasHiddenColumns()
1500   {
1501     return hiddenColumns != null && hiddenColumns.size() > 0;
1502   }
1503
1504   /**
1505    * 
1506    * @return true if there are more than one set of columns hidden
1507    */
1508   public boolean hasManyHiddenColumns()
1509   {
1510     return hiddenColumns != null && hiddenColumns.size() > 1;
1511   }
1512
1513   /**
1514    * mark the columns corresponding to gap characters as hidden in the column
1515    * selection
1516    * 
1517    * @param sr
1518    */
1519   public void hideInsertionsFor(SequenceI sr)
1520   {
1521     List<int[]> inserts = sr.getInsertions();
1522     for (int[] r : inserts)
1523     {
1524       hideColumns(r[0], r[1]);
1525     }
1526   }
1527
1528   public boolean filterAnnotations(Annotation[] annotations,
1529           AnnotationFilterParameter filterParams)
1530   {
1531     // JBPNote - this method needs to be refactored to become independent of
1532     // viewmodel package
1533     this.revealAllHiddenColumns();
1534     this.clear();
1535     int count = 0;
1536     do
1537     {
1538       if (annotations[count] != null)
1539       {
1540
1541         boolean itemMatched = false;
1542
1543         if (filterParams.getThresholdType() == AnnotationFilterParameter.ThresholdType.ABOVE_THRESHOLD
1544                 && annotations[count].value >= filterParams
1545                         .getThresholdValue())
1546         {
1547           itemMatched = true;
1548         }
1549         if (filterParams.getThresholdType() == AnnotationFilterParameter.ThresholdType.BELOW_THRESHOLD
1550                 && annotations[count].value <= filterParams
1551                         .getThresholdValue())
1552         {
1553           itemMatched = true;
1554         }
1555
1556         if (filterParams.isFilterAlphaHelix()
1557                 && annotations[count].secondaryStructure == 'H')
1558         {
1559           itemMatched = true;
1560         }
1561
1562         if (filterParams.isFilterBetaSheet()
1563                 && annotations[count].secondaryStructure == 'E')
1564         {
1565           itemMatched = true;
1566         }
1567
1568         if (filterParams.isFilterTurn()
1569                 && annotations[count].secondaryStructure == 'S')
1570         {
1571           itemMatched = true;
1572         }
1573
1574         String regexSearchString = filterParams.getRegexString();
1575         if (regexSearchString != null
1576                 && !filterParams.getRegexSearchFields().isEmpty())
1577         {
1578           List<SearchableAnnotationField> fields = filterParams
1579                   .getRegexSearchFields();
1580           try
1581           {
1582             if (fields.contains(SearchableAnnotationField.DISPLAY_STRING)
1583                     && annotations[count].displayCharacter
1584                             .matches(regexSearchString))
1585             {
1586               itemMatched = true;
1587             }
1588           } catch (java.util.regex.PatternSyntaxException pse)
1589           {
1590             if (annotations[count].displayCharacter
1591                     .equals(regexSearchString))
1592             {
1593               itemMatched = true;
1594             }
1595           }
1596           if (fields.contains(SearchableAnnotationField.DESCRIPTION)
1597                   && annotations[count].description != null
1598                   && annotations[count].description
1599                           .matches(regexSearchString))
1600           {
1601             itemMatched = true;
1602           }
1603         }
1604
1605         if (itemMatched)
1606         {
1607           this.addElement(count);
1608         }
1609       }
1610       count++;
1611     } while (count < annotations.length);
1612     return false;
1613   }
1614
1615 }