daea70d856252eca8048a85612e985caffef71a3
[jalview.git] / src / jalview / viewmodel / AlignmentViewport.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
3  * Copyright (C) 2014 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.viewmodel;
22
23 import jalview.analysis.Conservation;
24 import jalview.api.AlignCalcManagerI;
25 import jalview.api.AlignViewportI;
26 import jalview.api.AlignmentViewPanel;
27 import jalview.api.FeaturesDisplayedI;
28 import jalview.datamodel.AlignmentAnnotation;
29 import jalview.datamodel.AlignmentI;
30 import jalview.datamodel.AlignmentView;
31 import jalview.datamodel.Annotation;
32 import jalview.datamodel.ColumnSelection;
33 import jalview.datamodel.Sequence;
34 import jalview.datamodel.SequenceCollectionI;
35 import jalview.datamodel.SequenceGroup;
36 import jalview.datamodel.SequenceI;
37 import jalview.schemes.Blosum62ColourScheme;
38 import jalview.schemes.ColourSchemeI;
39 import jalview.schemes.PIDColourScheme;
40 import jalview.schemes.ResidueProperties;
41 import jalview.workers.AlignCalcManager;
42 import jalview.workers.ConsensusThread;
43 import jalview.workers.StrucConsensusThread;
44
45 import java.awt.Color;
46 import java.util.ArrayList;
47 import java.util.BitSet;
48 import java.util.Hashtable;
49 import java.util.List;
50 import java.util.Map;
51 import java.util.Vector;
52
53 /**
54  * base class holding visualization and analysis attributes and common logic for
55  * an active alignment view displayed in the GUI
56  * 
57  * @author jimp
58  * 
59  */
60 public abstract class AlignmentViewport implements AlignViewportI
61 {
62   /**
63    * alignment displayed in the viewport. Please use get/setter
64    */
65   protected AlignmentI alignment;
66
67   protected String sequenceSetID;
68
69   /**
70    * probably unused indicator that view is of a dataset rather than an
71    * alignment
72    */
73   protected boolean isDataset = false;
74
75   private Map<SequenceI, SequenceCollectionI> hiddenRepSequences;
76
77   protected ColumnSelection colSel = new ColumnSelection();
78
79   public boolean autoCalculateConsensus = true;
80
81   protected boolean autoCalculateStrucConsensus = true;
82
83   protected boolean ignoreGapsInConsensusCalculation = false;
84
85   protected ColourSchemeI globalColourScheme = null;
86
87   /**
88    * gui state - changes to colour scheme propagated to all groups
89    */
90   private boolean colourAppliesToAllGroups;
91
92   /**
93    * @param value
94    *          indicating if subsequent colourscheme changes will be propagated
95    *          to all groups
96    */
97   public void setColourAppliesToAllGroups(boolean b)
98   {
99     colourAppliesToAllGroups = b;
100   }
101
102   /**
103    * 
104    * 
105    * @return flag indicating if colourchanges propagated to all groups
106    */
107   public boolean getColourAppliesToAllGroups()
108   {
109     return colourAppliesToAllGroups;
110   }
111
112   boolean abovePIDThreshold = false;
113
114   /**
115    * GUI state
116    * 
117    * @return true if percent identity threshold is applied to shading
118    */
119   public boolean getAbovePIDThreshold()
120   {
121     return abovePIDThreshold;
122   }
123
124   /**
125    * GUI state
126    * 
127    * 
128    * @param b
129    *          indicate if percent identity threshold is applied to shading
130    */
131   public void setAbovePIDThreshold(boolean b)
132   {
133     abovePIDThreshold = b;
134   }
135
136   int threshold;
137
138   /**
139    * DOCUMENT ME!
140    * 
141    * @param thresh
142    *          DOCUMENT ME!
143    */
144   public void setThreshold(int thresh)
145   {
146     threshold = thresh;
147   }
148
149   /**
150    * DOCUMENT ME!
151    * 
152    * @return DOCUMENT ME!
153    */
154   public int getThreshold()
155   {
156     return threshold;
157   }
158
159   int increment;
160
161   /**
162    * 
163    * @param inc
164    *          set the scalar for bleaching colourschemes according to degree of
165    *          conservation
166    */
167   public void setIncrement(int inc)
168   {
169     increment = inc;
170   }
171
172   /**
173    * GUI State
174    * 
175    * @return get scalar for bleaching colourschemes by conservation
176    */
177   public int getIncrement()
178   {
179     return increment;
180   }
181
182   boolean conservationColourSelected = false;
183
184   /**
185    * GUI state
186    * 
187    * @return true if conservation based shading is enabled
188    */
189   public boolean getConservationSelected()
190   {
191     return conservationColourSelected;
192   }
193
194   /**
195    * GUI state
196    * 
197    * @param b
198    *          enable conservation based shading
199    */
200   public void setConservationSelected(boolean b)
201   {
202     conservationColourSelected = b;
203   }
204
205   @Override
206   public void setGlobalColourScheme(ColourSchemeI cs)
207   {
208     // TODO: logic refactored from AlignFrame changeColour -
209     // autorecalc stuff should be changed to rely on the worker system
210     // check to see if we should implement a changeColour(cs) method rather than
211     // put th logic in here
212     // - means that caller decides if they want to just modify state and defer
213     // calculation till later or to do all calculations in thread.
214     // via changecolour
215     globalColourScheme = cs;
216     boolean recalc = false;
217     if (cs != null)
218     {
219       cs.setConservationApplied(recalc = getConservationSelected());
220       if (getAbovePIDThreshold() || cs instanceof PIDColourScheme
221               || cs instanceof Blosum62ColourScheme)
222       {
223         recalc = true;
224         cs.setThreshold(threshold, ignoreGapsInConsensusCalculation);
225       }
226       else
227       {
228         cs.setThreshold(0, ignoreGapsInConsensusCalculation);
229       }
230       if (recalc)
231       {
232         cs.setConsensus(hconsensus);
233         cs.setConservation(hconservation);
234       }
235       cs.alignmentChanged(alignment, hiddenRepSequences);
236     }
237     if (getColourAppliesToAllGroups())
238     {
239       for (SequenceGroup sg : getAlignment().getGroups())
240       {
241         if (cs == null)
242         {
243           sg.cs = null;
244           continue;
245         }
246         sg.cs = cs.applyTo(sg, getHiddenRepSequences());
247         sg.setConsPercGaps(ConsPercGaps);
248         if (getAbovePIDThreshold() || cs instanceof PIDColourScheme
249                 || cs instanceof Blosum62ColourScheme)
250         {
251           sg.cs.setThreshold(threshold, getIgnoreGapsConsensus());
252           recalc = true;
253         }
254         else
255         {
256           sg.cs.setThreshold(0, getIgnoreGapsConsensus());
257         }
258
259         if (getConservationSelected())
260         {
261           sg.cs.setConservationApplied(true);
262           recalc = true;
263         }
264         else
265         {
266           sg.cs.setConservation(null);
267           // sg.cs.setThreshold(0, getIgnoreGapsConsensus());
268         }
269         if (recalc)
270         {
271           sg.recalcConservation();
272         }
273         else
274         {
275           sg.cs.alignmentChanged(sg, hiddenRepSequences);
276         }
277       }
278     }
279
280   }
281
282   @Override
283   public ColourSchemeI getGlobalColourScheme()
284   {
285     return globalColourScheme;
286   }
287
288   protected AlignmentAnnotation consensus;
289
290   protected AlignmentAnnotation strucConsensus;
291
292   protected AlignmentAnnotation conservation;
293
294   protected AlignmentAnnotation quality;
295
296   protected AlignmentAnnotation[] groupConsensus;
297
298   protected AlignmentAnnotation[] groupConservation;
299
300   /**
301    * results of alignment consensus analysis for visible portion of view
302    */
303   protected Hashtable[] hconsensus = null;
304
305   /**
306    * results of secondary structure base pair consensus for visible portion of
307    * view
308    */
309   protected Hashtable[] hStrucConsensus = null;
310
311   protected Conservation hconservation = null;
312
313   @Override
314   public void setConservation(Conservation cons)
315   {
316     hconservation = cons;
317   }
318
319   /**
320    * percentage gaps allowed in a column before all amino acid properties should
321    * be considered unconserved
322    */
323   int ConsPercGaps = 25; // JBPNote : This should be a scalable property!
324
325   @Override
326   public int getConsPercGaps()
327   {
328     return ConsPercGaps;
329   }
330
331   @Override
332   public void setSequenceConsensusHash(Hashtable[] hconsensus)
333   {
334     this.hconsensus = hconsensus;
335
336   }
337
338   @Override
339   public Hashtable[] getSequenceConsensusHash()
340   {
341     return hconsensus;
342   }
343
344   @Override
345   public Hashtable[] getRnaStructureConsensusHash()
346   {
347     return hStrucConsensus;
348   }
349
350   @Override
351   public void setRnaStructureConsensusHash(Hashtable[] hStrucConsensus)
352   {
353     this.hStrucConsensus = hStrucConsensus;
354
355   }
356
357   @Override
358   public AlignmentAnnotation getAlignmentQualityAnnot()
359   {
360     return quality;
361   }
362
363   @Override
364   public AlignmentAnnotation getAlignmentConservationAnnotation()
365   {
366     return conservation;
367   }
368
369   @Override
370   public AlignmentAnnotation getAlignmentConsensusAnnotation()
371   {
372     return consensus;
373   }
374
375   @Override
376   public AlignmentAnnotation getAlignmentStrucConsensusAnnotation()
377   {
378     return strucConsensus;
379   }
380
381   protected AlignCalcManagerI calculator = new AlignCalcManager();
382
383   /**
384    * trigger update of conservation annotation
385    */
386   public void updateConservation(final AlignmentViewPanel ap)
387   {
388     // see note in mantis : issue number 8585
389     if (alignment.isNucleotide() || conservation == null
390             || !autoCalculateConsensus)
391     {
392       return;
393     }
394     if (calculator
395             .getRegisteredWorkersOfClass(jalview.workers.ConservationThread.class) == null)
396     {
397       calculator.registerWorker(new jalview.workers.ConservationThread(
398               this, ap));
399     }
400   }
401
402   /**
403    * trigger update of consensus annotation
404    */
405   public void updateConsensus(final AlignmentViewPanel ap)
406   {
407     // see note in mantis : issue number 8585
408     if (consensus == null || !autoCalculateConsensus)
409     {
410       return;
411     }
412     if (calculator.getRegisteredWorkersOfClass(ConsensusThread.class) == null)
413     {
414       calculator.registerWorker(new ConsensusThread(this, ap));
415     }
416   }
417
418   // --------START Structure Conservation
419   public void updateStrucConsensus(final AlignmentViewPanel ap)
420   {
421     if (autoCalculateStrucConsensus && strucConsensus == null
422             && alignment.isNucleotide() && alignment.hasRNAStructure())
423     {
424       // secondary structure has been added - so init the consensus line
425       initRNAStructure();
426     }
427
428     // see note in mantis : issue number 8585
429     if (strucConsensus == null || !autoCalculateStrucConsensus)
430     {
431       return;
432     }
433     if (calculator.getRegisteredWorkersOfClass(StrucConsensusThread.class) == null)
434     {
435       calculator.registerWorker(new StrucConsensusThread(this, ap));
436     }
437   }
438
439   public boolean isCalcInProgress()
440   {
441     return calculator.isWorking();
442   }
443
444   @Override
445   public boolean isCalculationInProgress(
446           AlignmentAnnotation alignmentAnnotation)
447   {
448     if (!alignmentAnnotation.autoCalculated)
449     {
450       return false;
451     }
452     if (calculator.workingInvolvedWith(alignmentAnnotation))
453     {
454       // System.err.println("grey out ("+alignmentAnnotation.label+")");
455       return true;
456     }
457     return false;
458   }
459
460   @Override
461   public boolean isClosed()
462   {
463     // TODO: check that this isClosed is only true after panel is closed, not
464     // before it is fully constructed.
465     return alignment == null;
466   }
467
468   @Override
469   public AlignCalcManagerI getCalcManager()
470   {
471     return calculator;
472   }
473
474   /**
475    * should conservation rows be shown for groups
476    */
477   protected boolean showGroupConservation = false;
478
479   /**
480    * should consensus rows be shown for groups
481    */
482   protected boolean showGroupConsensus = false;
483
484   /**
485    * should consensus profile be rendered by default
486    */
487   protected boolean showSequenceLogo = false;
488
489   /**
490    * should consensus profile be rendered normalised to row height
491    */
492   protected boolean normaliseSequenceLogo = false;
493
494   /**
495    * should consensus histograms be rendered by default
496    */
497   protected boolean showConsensusHistogram = true;
498
499   /**
500    * @return the showConsensusProfile
501    */
502   @Override
503   public boolean isShowSequenceLogo()
504   {
505     return showSequenceLogo;
506   }
507
508   /**
509    * @param showSequenceLogo
510    *          the new value
511    */
512   public void setShowSequenceLogo(boolean showSequenceLogo)
513   {
514     if (showSequenceLogo != this.showSequenceLogo)
515     {
516       // TODO: decouple settings setting from calculation when refactoring
517       // annotation update method from alignframe to viewport
518       this.showSequenceLogo = showSequenceLogo;
519       calculator.updateAnnotationFor(ConsensusThread.class);
520       calculator.updateAnnotationFor(StrucConsensusThread.class);
521     }
522     this.showSequenceLogo = showSequenceLogo;
523   }
524
525   /**
526    * @param showConsensusHistogram
527    *          the showConsensusHistogram to set
528    */
529   public void setShowConsensusHistogram(boolean showConsensusHistogram)
530   {
531     this.showConsensusHistogram = showConsensusHistogram;
532   }
533
534   /**
535    * @return the showGroupConservation
536    */
537   public boolean isShowGroupConservation()
538   {
539     return showGroupConservation;
540   }
541
542   /**
543    * @param showGroupConservation
544    *          the showGroupConservation to set
545    */
546   public void setShowGroupConservation(boolean showGroupConservation)
547   {
548     this.showGroupConservation = showGroupConservation;
549   }
550
551   /**
552    * @return the showGroupConsensus
553    */
554   public boolean isShowGroupConsensus()
555   {
556     return showGroupConsensus;
557   }
558
559   /**
560    * @param showGroupConsensus
561    *          the showGroupConsensus to set
562    */
563   public void setShowGroupConsensus(boolean showGroupConsensus)
564   {
565     this.showGroupConsensus = showGroupConsensus;
566   }
567
568   /**
569    * 
570    * @return flag to indicate if the consensus histogram should be rendered by
571    *         default
572    */
573   @Override
574   public boolean isShowConsensusHistogram()
575   {
576     return this.showConsensusHistogram;
577   }
578
579   /**
580    * show non-conserved residues only
581    */
582   protected boolean showUnconserved = false;
583
584   /**
585    * when set, updateAlignment will always ensure sequences are of equal length
586    */
587   private boolean padGaps = false;
588
589   /**
590    * when set, alignment should be reordered according to a newly opened tree
591    */
592   public boolean sortByTree = false;
593
594   public boolean getShowUnconserved()
595   {
596     return showUnconserved;
597   }
598
599   public void setShowUnconserved(boolean showunconserved)
600   {
601     showUnconserved = showunconserved;
602   }
603
604   /**
605    * @param showNonconserved
606    *          the showUnconserved to set
607    */
608   public void setShowunconserved(boolean displayNonconserved)
609   {
610     this.showUnconserved = displayNonconserved;
611   }
612
613   /**
614    * 
615    * 
616    * @return null or the currently selected sequence region
617    */
618   @Override
619   public SequenceGroup getSelectionGroup()
620   {
621     return selectionGroup;
622   }
623
624   /**
625    * Set the selection group for this window.
626    * 
627    * @param sg
628    *          - group holding references to sequences in this alignment view
629    * 
630    */
631   @Override
632   public void setSelectionGroup(SequenceGroup sg)
633   {
634     selectionGroup = sg;
635   }
636
637   public void setHiddenColumns(ColumnSelection colsel)
638   {
639     this.colSel = colsel;
640     if (colSel.getHiddenColumns() != null)
641     {
642       hasHiddenColumns = true;
643     }
644   }
645
646   @Override
647   public ColumnSelection getColumnSelection()
648   {
649     return colSel;
650   }
651
652   @Override
653   public void setColumnSelection(ColumnSelection colSel)
654   {
655     this.colSel = colSel;
656     if (colSel != null)
657     {
658       updateHiddenColumns();
659     }
660   }
661
662   /**
663    * 
664    * @return
665    */
666   @Override
667   public Map<SequenceI, SequenceCollectionI> getHiddenRepSequences()
668   {
669     return hiddenRepSequences;
670   }
671
672   @Override
673   public void setHiddenRepSequences(
674           Map<SequenceI, SequenceCollectionI> hiddenRepSequences)
675   {
676     this.hiddenRepSequences = hiddenRepSequences;
677   }
678
679   protected boolean hasHiddenColumns = false;
680
681   public void updateHiddenColumns()
682   {
683     hasHiddenColumns = colSel.hasHiddenColumns();
684   }
685
686   protected boolean hasHiddenRows = false;
687
688   public boolean hasHiddenRows()
689   {
690     return hasHiddenRows;
691   }
692
693   protected SequenceGroup selectionGroup;
694
695   public void setSequenceSetId(String newid)
696   {
697     if (sequenceSetID != null)
698     {
699       System.err
700               .println("Warning - overwriting a sequenceSetId for a viewport!");
701     }
702     sequenceSetID = new String(newid);
703   }
704
705   @Override
706   public String getSequenceSetId()
707   {
708     if (sequenceSetID == null)
709     {
710       sequenceSetID = alignment.hashCode() + "";
711     }
712
713     return sequenceSetID;
714   }
715
716   /**
717    * unique viewId for synchronizing state (e.g. with stored Jalview Project)
718    * 
719    */
720   protected String viewId = null;
721
722   public String getViewId()
723   {
724     if (viewId == null)
725     {
726       viewId = this.getSequenceSetId() + "." + this.hashCode() + "";
727     }
728     return viewId;
729   }
730
731   public void setIgnoreGapsConsensus(boolean b, AlignmentViewPanel ap)
732   {
733     ignoreGapsInConsensusCalculation = b;
734     if (ap != null)
735     {
736       updateConsensus(ap);
737       if (globalColourScheme != null)
738       {
739         globalColourScheme.setThreshold(globalColourScheme.getThreshold(),
740                 ignoreGapsInConsensusCalculation);
741       }
742     }
743
744   }
745
746   private long sgrouphash = -1, colselhash = -1;
747
748   /**
749    * checks current SelectionGroup against record of last hash value, and
750    * updates record.
751    * 
752    * @param b
753    *          update the record of last hash value
754    * 
755    * @return true if SelectionGroup changed since last call (when b is true)
756    */
757   public boolean isSelectionGroupChanged(boolean b)
758   {
759     int hc = (selectionGroup == null || selectionGroup.getSize() == 0) ? -1
760             : selectionGroup.hashCode();
761     if (hc != -1 && hc != sgrouphash)
762     {
763       if (b)
764       {
765         sgrouphash = hc;
766       }
767       return true;
768     }
769     return false;
770   }
771
772   /**
773    * checks current colsel against record of last hash value, and optionally
774    * updates record.
775    * 
776    * @param b
777    *          update the record of last hash value
778    * @return true if colsel changed since last call (when b is true)
779    */
780   public boolean isColSelChanged(boolean b)
781   {
782     int hc = (colSel == null || colSel.size() == 0) ? -1 : colSel
783             .hashCode();
784     if (hc != -1 && hc != colselhash)
785     {
786       if (b)
787       {
788         colselhash = hc;
789       }
790       return true;
791     }
792     return false;
793   }
794
795   @Override
796   public boolean getIgnoreGapsConsensus()
797   {
798     return ignoreGapsInConsensusCalculation;
799   }
800
801   // / property change stuff
802
803   // JBPNote Prolly only need this in the applet version.
804   private final java.beans.PropertyChangeSupport changeSupport = new java.beans.PropertyChangeSupport(
805           this);
806
807   protected boolean showConservation = true;
808
809   protected boolean showQuality = true;
810
811   protected boolean showConsensus = true;
812
813   Hashtable sequenceColours;
814
815   /**
816    * Property change listener for changes in alignment
817    * 
818    * @param listener
819    *          DOCUMENT ME!
820    */
821   public void addPropertyChangeListener(
822           java.beans.PropertyChangeListener listener)
823   {
824     changeSupport.addPropertyChangeListener(listener);
825   }
826
827   /**
828    * DOCUMENT ME!
829    * 
830    * @param listener
831    *          DOCUMENT ME!
832    */
833   public void removePropertyChangeListener(
834           java.beans.PropertyChangeListener listener)
835   {
836     changeSupport.removePropertyChangeListener(listener);
837   }
838
839   /**
840    * Property change listener for changes in alignment
841    * 
842    * @param prop
843    *          DOCUMENT ME!
844    * @param oldvalue
845    *          DOCUMENT ME!
846    * @param newvalue
847    *          DOCUMENT ME!
848    */
849   public void firePropertyChange(String prop, Object oldvalue,
850           Object newvalue)
851   {
852     changeSupport.firePropertyChange(prop, oldvalue, newvalue);
853   }
854
855   // common hide/show column stuff
856
857   public void hideSelectedColumns()
858   {
859     if (colSel.size() < 1)
860     {
861       return;
862     }
863
864     colSel.hideSelectedColumns();
865     setSelectionGroup(null);
866
867     hasHiddenColumns = true;
868   }
869
870   public void hideColumns(int start, int end)
871   {
872     if (start == end)
873     {
874       colSel.hideColumns(start);
875     }
876     else
877     {
878       colSel.hideColumns(start, end);
879     }
880
881     hasHiddenColumns = true;
882   }
883
884   public void showColumn(int col)
885   {
886     colSel.revealHiddenColumns(col);
887     if (colSel.getHiddenColumns() == null)
888     {
889       hasHiddenColumns = false;
890     }
891   }
892
893   public void showAllHiddenColumns()
894   {
895     colSel.revealAllHiddenColumns();
896     hasHiddenColumns = false;
897   }
898
899   // common hide/show seq stuff
900   public void showAllHiddenSeqs()
901   {
902     if (alignment.getHiddenSequences().getSize() > 0)
903     {
904       if (selectionGroup == null)
905       {
906         selectionGroup = new SequenceGroup();
907         selectionGroup.setEndRes(alignment.getWidth() - 1);
908       }
909       List<SequenceI> tmp = alignment.getHiddenSequences().showAll(
910               hiddenRepSequences);
911       for (SequenceI seq : tmp)
912       {
913         selectionGroup.addSequence(seq, false);
914         setSequenceAnnotationsVisible(seq, true);
915       }
916
917       hasHiddenRows = false;
918       hiddenRepSequences = null;
919
920       firePropertyChange("alignment", null, alignment.getSequences());
921       // used to set hasHiddenRows/hiddenRepSequences here, after the property
922       // changed event
923       sendSelection();
924     }
925   }
926
927   public void showSequence(int index)
928   {
929     List<SequenceI> tmp = alignment.getHiddenSequences().showSequence(
930             index,
931             hiddenRepSequences);
932     if (tmp.size() > 0)
933     {
934       if (selectionGroup == null)
935       {
936         selectionGroup = new SequenceGroup();
937         selectionGroup.setEndRes(alignment.getWidth() - 1);
938       }
939
940       for (SequenceI seq : tmp)
941       {
942         selectionGroup.addSequence(seq, false);
943         setSequenceAnnotationsVisible(seq, true);
944       }
945       // JBPNote: refactor: only update flag if we modified visiblity (used to
946       // do this regardless)
947       if (alignment.getHiddenSequences().getSize() < 1)
948       {
949         hasHiddenRows = false;
950       }
951       firePropertyChange("alignment", null, alignment.getSequences());
952       sendSelection();
953     }
954   }
955
956   public void hideAllSelectedSeqs()
957   {
958     if (selectionGroup == null || selectionGroup.getSize() < 1)
959     {
960       return;
961     }
962
963     SequenceI[] seqs = selectionGroup.getSequencesInOrder(alignment);
964
965     hideSequence(seqs);
966
967     setSelectionGroup(null);
968   }
969
970   public void hideSequence(SequenceI[] seq)
971   {
972     if (seq != null)
973     {
974       for (int i = 0; i < seq.length; i++)
975       {
976         alignment.getHiddenSequences().hideSequence(seq[i]);
977         setSequenceAnnotationsVisible(seq[i], false);
978       }
979       hasHiddenRows = true;
980       firePropertyChange("alignment", null, alignment.getSequences());
981     }
982   }
983
984   /**
985    * Set visibility for any annotations for the given sequence.
986    * 
987    * @param sequenceI
988    */
989   protected void setSequenceAnnotationsVisible(SequenceI sequenceI,
990           boolean visible)
991   {
992     for (AlignmentAnnotation ann : alignment.getAlignmentAnnotation())
993     {
994       if (ann.sequenceRef == sequenceI)
995       {
996         ann.visible = visible;
997       }
998     }
999   }
1000
1001   public void hideRepSequences(SequenceI repSequence, SequenceGroup sg)
1002   {
1003     int sSize = sg.getSize();
1004     if (sSize < 2)
1005     {
1006       return;
1007     }
1008
1009     if (hiddenRepSequences == null)
1010     {
1011       hiddenRepSequences = new Hashtable();
1012     }
1013
1014     hiddenRepSequences.put(repSequence, sg);
1015
1016     // Hide all sequences except the repSequence
1017     SequenceI[] seqs = new SequenceI[sSize - 1];
1018     int index = 0;
1019     for (int i = 0; i < sSize; i++)
1020     {
1021       if (sg.getSequenceAt(i) != repSequence)
1022       {
1023         if (index == sSize - 1)
1024         {
1025           return;
1026         }
1027
1028         seqs[index++] = sg.getSequenceAt(i);
1029       }
1030     }
1031     sg.setSeqrep(repSequence); // note: not done in 2.7applet
1032     sg.setHidereps(true); // note: not done in 2.7applet
1033     hideSequence(seqs);
1034
1035   }
1036
1037   public boolean isHiddenRepSequence(SequenceI seq)
1038   {
1039     return alignment.getSeqrep()==seq || (hiddenRepSequences != null
1040             && hiddenRepSequences.containsKey(seq));
1041   }
1042
1043   public SequenceGroup getRepresentedSequences(SequenceI seq)
1044   {
1045     return (SequenceGroup) (hiddenRepSequences == null ? null
1046             : hiddenRepSequences.get(seq));
1047   }
1048
1049   public int adjustForHiddenSeqs(int alignmentIndex)
1050   {
1051     return alignment.getHiddenSequences().adjustForHiddenSeqs(
1052             alignmentIndex);
1053   }
1054
1055   // Selection manipulation
1056   /**
1057    * broadcast selection to any interested parties
1058    */
1059   public abstract void sendSelection();
1060
1061   public void invertColumnSelection()
1062   {
1063     colSel.invertColumnSelection(0, alignment.getWidth());
1064   }
1065
1066   /**
1067    * This method returns an array of new SequenceI objects derived from the
1068    * whole alignment or just the current selection with start and end points
1069    * adjusted
1070    * 
1071    * @note if you need references to the actual SequenceI objects in the
1072    *       alignment or currently selected then use getSequenceSelection()
1073    * @return selection as new sequenceI objects
1074    */
1075   public SequenceI[] getSelectionAsNewSequence()
1076   {
1077     SequenceI[] sequences;
1078     // JBPNote: Need to test jalviewLite.getSelectedSequencesAsAlignmentFrom -
1079     // this was the only caller in the applet for this method
1080     // JBPNote: in applet, this method returned references to the alignment
1081     // sequences, and it did not honour the presence/absence of annotation
1082     // attached to the alignment (probably!)
1083     if (selectionGroup == null || selectionGroup.getSize() == 0)
1084     {
1085       sequences = alignment.getSequencesArray();
1086       AlignmentAnnotation[] annots = alignment.getAlignmentAnnotation();
1087       for (int i = 0; i < sequences.length; i++)
1088       {
1089         sequences[i] = new Sequence(sequences[i], annots); // construct new
1090         // sequence with
1091         // subset of visible
1092         // annotation
1093       }
1094     }
1095     else
1096     {
1097       sequences = selectionGroup.getSelectionAsNewSequences(alignment);
1098     }
1099
1100     return sequences;
1101   }
1102
1103   /**
1104    * get the currently selected sequence objects or all the sequences in the
1105    * alignment.
1106    * 
1107    * @return array of references to sequence objects
1108    */
1109   @Override
1110   public SequenceI[] getSequenceSelection()
1111   {
1112     SequenceI[] sequences = null;
1113     if (selectionGroup != null)
1114     {
1115       sequences = selectionGroup.getSequencesInOrder(alignment);
1116     }
1117     if (sequences == null)
1118     {
1119       sequences = alignment.getSequencesArray();
1120     }
1121     return sequences;
1122   }
1123
1124   /**
1125    * This method returns the visible alignment as text, as seen on the GUI, ie
1126    * if columns are hidden they will not be returned in the result. Use this for
1127    * calculating trees, PCA, redundancy etc on views which contain hidden
1128    * columns.
1129    * 
1130    * @return String[]
1131    */
1132   @Override
1133   public jalview.datamodel.CigarArray getViewAsCigars(
1134           boolean selectedRegionOnly)
1135   {
1136     return new jalview.datamodel.CigarArray(alignment,
1137             (hasHiddenColumns ? colSel : null),
1138             (selectedRegionOnly ? selectionGroup : null));
1139   }
1140
1141   /**
1142    * return a compact representation of the current alignment selection to pass
1143    * to an analysis function
1144    * 
1145    * @param selectedOnly
1146    *          boolean true to just return the selected view
1147    * @return AlignmentView
1148    */
1149   @Override
1150   public jalview.datamodel.AlignmentView getAlignmentView(
1151           boolean selectedOnly)
1152   {
1153     return getAlignmentView(selectedOnly, false);
1154   }
1155
1156   /**
1157    * return a compact representation of the current alignment selection to pass
1158    * to an analysis function
1159    * 
1160    * @param selectedOnly
1161    *          boolean true to just return the selected view
1162    * @param markGroups
1163    *          boolean true to annotate the alignment view with groups on the
1164    *          alignment (and intersecting with selected region if selectedOnly
1165    *          is true)
1166    * @return AlignmentView
1167    */
1168   @Override
1169   public jalview.datamodel.AlignmentView getAlignmentView(
1170           boolean selectedOnly, boolean markGroups)
1171   {
1172     return new AlignmentView(alignment, colSel, selectionGroup,
1173             hasHiddenColumns, selectedOnly, markGroups);
1174   }
1175
1176   /**
1177    * This method returns the visible alignment as text, as seen on the GUI, ie
1178    * if columns are hidden they will not be returned in the result. Use this for
1179    * calculating trees, PCA, redundancy etc on views which contain hidden
1180    * columns.
1181    * 
1182    * @return String[]
1183    */
1184   @Override
1185   public String[] getViewAsString(boolean selectedRegionOnly)
1186   {
1187     String[] selection = null;
1188     SequenceI[] seqs = null;
1189     int i, iSize;
1190     int start = 0, end = 0;
1191     if (selectedRegionOnly && selectionGroup != null)
1192     {
1193       iSize = selectionGroup.getSize();
1194       seqs = selectionGroup.getSequencesInOrder(alignment);
1195       start = selectionGroup.getStartRes();
1196       end = selectionGroup.getEndRes() + 1;
1197     }
1198     else
1199     {
1200       iSize = alignment.getHeight();
1201       seqs = alignment.getSequencesArray();
1202       end = alignment.getWidth();
1203     }
1204
1205     selection = new String[iSize];
1206     if (hasHiddenColumns)
1207     {
1208       selection = colSel.getVisibleSequenceStrings(start, end, seqs);
1209     }
1210     else
1211     {
1212       for (i = 0; i < iSize; i++)
1213       {
1214         selection[i] = seqs[i].getSequenceAsString(start, end);
1215       }
1216
1217     }
1218     return selection;
1219   }
1220
1221   /**
1222    * return visible region boundaries within given column range
1223    * 
1224    * @param min
1225    *          first column (inclusive, from 0)
1226    * @param max
1227    *          last column (exclusive)
1228    * @return int[][] range of {start,end} visible positions
1229    */
1230   public int[][] getVisibleRegionBoundaries(int min, int max)
1231   {
1232     Vector regions = new Vector();
1233     int start = min;
1234     int end = max;
1235
1236     do
1237     {
1238       if (hasHiddenColumns)
1239       {
1240         if (start == 0)
1241         {
1242           start = colSel.adjustForHiddenColumns(start);
1243         }
1244
1245         end = colSel.getHiddenBoundaryRight(start);
1246         if (start == end)
1247         {
1248           end = max;
1249         }
1250         if (end > max)
1251         {
1252           end = max;
1253         }
1254       }
1255
1256       regions.addElement(new int[]
1257       { start, end });
1258
1259       if (hasHiddenColumns)
1260       {
1261         start = colSel.adjustForHiddenColumns(end);
1262         start = colSel.getHiddenBoundaryLeft(start) + 1;
1263       }
1264     } while (end < max);
1265
1266     int[][] startEnd = new int[regions.size()][2];
1267
1268     regions.copyInto(startEnd);
1269
1270     return startEnd;
1271
1272   }
1273
1274   @Override
1275   public List<AlignmentAnnotation> getVisibleAlignmentAnnotation(boolean selectedOnly)
1276   {
1277     ArrayList<AlignmentAnnotation> ala = new ArrayList<AlignmentAnnotation>();
1278     AlignmentAnnotation[] aa;
1279     if ((aa=alignment.getAlignmentAnnotation())!=null)
1280     {
1281       for (AlignmentAnnotation annot:aa)
1282       {
1283         AlignmentAnnotation clone = new AlignmentAnnotation(annot);
1284         if (selectedOnly && selectionGroup!=null)
1285         {
1286           colSel.makeVisibleAnnotation(selectionGroup.getStartRes(), selectionGroup.getEndRes(),clone);
1287         } else {
1288           colSel.makeVisibleAnnotation(clone);
1289         }
1290         ala.add(clone);
1291       }
1292     }
1293     return ala;
1294   }
1295
1296   /**
1297    * @return the padGaps
1298    */
1299   public boolean isPadGaps()
1300   {
1301     return padGaps;
1302   }
1303
1304   /**
1305    * @param padGaps
1306    *          the padGaps to set
1307    */
1308   public void setPadGaps(boolean padGaps)
1309   {
1310     this.padGaps = padGaps;
1311   }
1312
1313   /**
1314    * apply any post-edit constraints and trigger any calculations needed after
1315    * an edit has been performed on the alignment
1316    * 
1317    * @param ap
1318    */
1319   public void alignmentChanged(AlignmentViewPanel ap)
1320   {
1321     if (isPadGaps())
1322     {
1323       alignment.padGaps();
1324     }
1325     if (autoCalculateConsensus)
1326     {
1327       updateConsensus(ap);
1328     }
1329     if (hconsensus != null && autoCalculateConsensus)
1330     {
1331       updateConservation(ap);
1332     }
1333     if (autoCalculateStrucConsensus)
1334     {
1335       updateStrucConsensus(ap);
1336     }
1337
1338     // Reset endRes of groups if beyond alignment width
1339     int alWidth = alignment.getWidth();
1340     List<SequenceGroup> groups = alignment.getGroups();
1341     if (groups != null)
1342     {
1343       for (SequenceGroup sg : groups)
1344       {
1345         if (sg.getEndRes() > alWidth)
1346         {
1347           sg.setEndRes(alWidth - 1);
1348         }
1349       }
1350     }
1351
1352     if (selectionGroup != null && selectionGroup.getEndRes() > alWidth)
1353     {
1354       selectionGroup.setEndRes(alWidth - 1);
1355     }
1356
1357     resetAllColourSchemes();
1358     calculator.restartWorkers();
1359     // alignment.adjustSequenceAnnotations();
1360   }
1361
1362   /**
1363    * reset scope and do calculations for all applied colourschemes on alignment
1364    */
1365   void resetAllColourSchemes()
1366   {
1367     ColourSchemeI cs = globalColourScheme;
1368     if (cs != null)
1369     {
1370       cs.alignmentChanged(alignment, hiddenRepSequences);
1371
1372       cs.setConsensus(hconsensus);
1373       if (cs.conservationApplied())
1374       {
1375         cs.setConservation(Conservation.calculateConservation("All",
1376                 ResidueProperties.propHash, 3, alignment.getSequences(), 0,
1377                 alignment.getWidth(), false, getConsPercGaps(), false));
1378       }
1379     }
1380
1381     for (SequenceGroup sg : alignment.getGroups())
1382     {
1383       if (sg.cs != null)
1384       {
1385         sg.cs.alignmentChanged(sg, hiddenRepSequences);
1386       }
1387       sg.recalcConservation();
1388     }
1389   }
1390
1391   protected void initAutoAnnotation()
1392   {
1393     // TODO: add menu option action that nulls or creates consensus object
1394     // depending on if the user wants to see the annotation or not in a
1395     // specific alignment
1396
1397     if (hconsensus == null && !isDataset)
1398     {
1399       if (!alignment.isNucleotide())
1400       {
1401         initConservation();
1402         initQuality();
1403       }
1404       else
1405       {
1406         initRNAStructure();
1407       }
1408       initConsensus();
1409     }
1410   }
1411
1412   private void initConsensus()
1413   {
1414
1415     consensus = new AlignmentAnnotation("Consensus", "PID",
1416             new Annotation[1], 0f, 100f, AlignmentAnnotation.BAR_GRAPH);
1417     consensus.hasText = true;
1418     consensus.autoCalculated = true;
1419
1420     if (showConsensus)
1421     {
1422       alignment.addAnnotation(consensus);
1423     }
1424   }
1425
1426   private void initConservation()
1427   {
1428     if (showConservation)
1429     {
1430       if (conservation == null)
1431       {
1432         conservation = new AlignmentAnnotation("Conservation",
1433                 "Conservation of total alignment less than "
1434                         + getConsPercGaps() + "% gaps", new Annotation[1],
1435                 0f, 11f, AlignmentAnnotation.BAR_GRAPH);
1436         conservation.hasText = true;
1437         conservation.autoCalculated = true;
1438         alignment.addAnnotation(conservation);
1439       }
1440     }
1441   }
1442
1443   private void initQuality()
1444   {
1445     if (showQuality)
1446     {
1447       if (quality == null)
1448       {
1449         quality = new AlignmentAnnotation("Quality",
1450                 "Alignment Quality based on Blosum62 scores",
1451                 new Annotation[1], 0f, 11f, AlignmentAnnotation.BAR_GRAPH);
1452         quality.hasText = true;
1453         quality.autoCalculated = true;
1454         alignment.addAnnotation(quality);
1455       }
1456     }
1457   }
1458
1459   private void initRNAStructure()
1460   {
1461     if (alignment.hasRNAStructure() && strucConsensus == null)
1462     {
1463       strucConsensus = new AlignmentAnnotation("StrucConsensus", "PID",
1464               new Annotation[1], 0f, 100f, AlignmentAnnotation.BAR_GRAPH);
1465       strucConsensus.hasText = true;
1466       strucConsensus.autoCalculated = true;
1467
1468       if (showConsensus)
1469       {
1470         alignment.addAnnotation(strucConsensus);
1471       }
1472     }
1473   }
1474
1475   /*
1476    * (non-Javadoc)
1477    * 
1478    * @see jalview.api.AlignViewportI#calcPanelHeight()
1479    */
1480   public int calcPanelHeight()
1481   {
1482     // setHeight of panels
1483     AlignmentAnnotation[] aa = getAlignment().getAlignmentAnnotation();
1484     int height = 0;
1485     int charHeight = getCharHeight();
1486     if (aa != null)
1487     {
1488       BitSet graphgrp = new BitSet();
1489       for (int i = 0; i < aa.length; i++)
1490       {
1491         if (aa[i] == null)
1492         {
1493           System.err.println("Null annotation row: ignoring.");
1494           continue;
1495         }
1496         if (!aa[i].visible)
1497         {
1498           continue;
1499         }
1500         if (aa[i].graphGroup > -1)
1501         {
1502           if (graphgrp.get(aa[i].graphGroup))
1503           {
1504             continue;
1505           }
1506           else
1507           {
1508             graphgrp.set(aa[i].graphGroup);
1509           }
1510         }
1511         aa[i].height = 0;
1512
1513         if (aa[i].hasText)
1514         {
1515           aa[i].height += charHeight;
1516         }
1517
1518         if (aa[i].hasIcons)
1519         {
1520           aa[i].height += 16;
1521         }
1522
1523         if (aa[i].graph > 0)
1524         {
1525           aa[i].height += aa[i].graphHeight;
1526         }
1527
1528         if (aa[i].height == 0)
1529         {
1530           aa[i].height = 20;
1531         }
1532
1533         height += aa[i].height;
1534       }
1535     }
1536     if (height == 0)
1537     {
1538       // set minimum
1539       height = 20;
1540     }
1541     return height;
1542   }
1543
1544   @Override
1545   public void updateGroupAnnotationSettings(boolean applyGlobalSettings,
1546           boolean preserveNewGroupSettings)
1547   {
1548     boolean updateCalcs = false;
1549     boolean conv = isShowGroupConservation();
1550     boolean cons = isShowGroupConsensus();
1551     boolean showprf = isShowSequenceLogo();
1552     boolean showConsHist = isShowConsensusHistogram();
1553     boolean normLogo = isNormaliseSequenceLogo();
1554
1555     /**
1556      * TODO reorder the annotation rows according to group/sequence ordering on
1557      * alignment
1558      */
1559     boolean sortg = true;
1560
1561     // remove old automatic annotation
1562     // add any new annotation
1563
1564     // intersect alignment annotation with alignment groups
1565
1566     AlignmentAnnotation[] aan = alignment.getAlignmentAnnotation();
1567     List<SequenceGroup> oldrfs = new ArrayList<SequenceGroup>();
1568     if (aan != null)
1569     {
1570       for (int an = 0; an < aan.length; an++)
1571       {
1572         if (aan[an].autoCalculated && aan[an].groupRef != null)
1573         {
1574           oldrfs.add(aan[an].groupRef);
1575           alignment.deleteAnnotation(aan[an], false);
1576         }
1577       }
1578     }
1579     if (alignment.getGroups() != null)
1580     {
1581       for (SequenceGroup sg : alignment.getGroups())
1582       {
1583         updateCalcs = false;
1584         if (applyGlobalSettings
1585                 || (!preserveNewGroupSettings && !oldrfs.contains(sg)))
1586         {
1587           // set defaults for this group's conservation/consensus
1588           sg.setshowSequenceLogo(showprf);
1589           sg.setShowConsensusHistogram(showConsHist);
1590           sg.setNormaliseSequenceLogo(normLogo);
1591         }
1592         if (conv)
1593         {
1594           updateCalcs = true;
1595           alignment.addAnnotation(sg.getConservationRow(), 0);
1596         }
1597         if (cons)
1598         {
1599           updateCalcs = true;
1600           alignment.addAnnotation(sg.getConsensus(), 0);
1601         }
1602         // refresh the annotation rows
1603         if (updateCalcs)
1604         {
1605           sg.recalcConservation();
1606         }
1607       }
1608     }
1609     oldrfs.clear();
1610   }
1611   /**
1612    * show the reference sequence in the alignment view
1613    */
1614   private boolean displayReferenceSeq=false;
1615   /**
1616    * colour according to the reference sequence defined on the alignment
1617    */
1618   private boolean colourByReferenceSeq=false;
1619
1620   public boolean isDisplayReferenceSeq()
1621   {
1622     return alignment.hasSeqrep() && displayReferenceSeq;
1623   }
1624
1625   public void setDisplayReferenceSeq(boolean displayReferenceSeq)
1626   {
1627     this.displayReferenceSeq = displayReferenceSeq;
1628   }
1629
1630   public boolean isColourByReferenceSeq()
1631   {
1632     return alignment.hasSeqrep() && colourByReferenceSeq;
1633   }
1634
1635   public void setColourByReferenceSeq(boolean colourByReferenceSeq)
1636   {
1637     this.colourByReferenceSeq = colourByReferenceSeq;
1638   }
1639
1640   @Override
1641   public Color getSequenceColour(SequenceI seq)
1642   {
1643     Color sqc = Color.white;
1644     if (sequenceColours != null)
1645     {
1646       sqc = (Color) sequenceColours.get(seq);
1647       if (sqc == null)
1648       {
1649         sqc = Color.white;
1650       }
1651     }
1652     return sqc;
1653   }
1654
1655   @Override
1656   public void setSequenceColour(SequenceI seq, Color col)
1657   {
1658     if (sequenceColours == null)
1659     {
1660       sequenceColours = new Hashtable();
1661     }
1662
1663     if (col == null)
1664     {
1665       sequenceColours.remove(seq);
1666     }
1667     else
1668     {
1669       sequenceColours.put(seq, col);
1670     }
1671   }
1672
1673   @Override
1674   public void updateSequenceIdColours()
1675   {
1676     if (sequenceColours == null)
1677     {
1678       sequenceColours = new Hashtable();
1679     }
1680     for (SequenceGroup sg : alignment.getGroups())
1681     {
1682       if (sg.idColour != null)
1683       {
1684         for (SequenceI s : sg.getSequences(getHiddenRepSequences()))
1685         {
1686           sequenceColours.put(s, sg.idColour);
1687         }
1688       }
1689     }
1690   }
1691
1692   @Override
1693   public void clearSequenceColours()
1694   {
1695     sequenceColours = null;
1696   };
1697
1698   FeaturesDisplayedI featuresDisplayed = null;
1699
1700   @Override
1701   public FeaturesDisplayedI getFeaturesDisplayed()
1702   {
1703     return featuresDisplayed;
1704   }
1705
1706   public void setFeaturesDisplayed(FeaturesDisplayedI featuresDisplayedI)
1707   {
1708     featuresDisplayed = featuresDisplayedI;
1709   }
1710
1711   public boolean areFeaturesDisplayed()
1712   {
1713     return featuresDisplayed != null && featuresDisplayed.getRegisterdFeaturesCount()>0;
1714   }
1715
1716   /**
1717    * display setting for showing/hiding sequence features on alignment view
1718    */
1719   boolean showSequenceFeatures = false;
1720
1721   /**
1722    * set the flag
1723    * 
1724    * @param b
1725    *          features are displayed if true
1726    */
1727   @Override
1728   public void setShowSequenceFeatures(boolean b)
1729   {
1730     showSequenceFeatures = b;
1731   }
1732   @Override
1733   public boolean isShowSequenceFeatures()
1734   {
1735     return showSequenceFeatures;
1736   }
1737
1738   boolean showSeqFeaturesHeight;
1739
1740   public void setShowSequenceFeaturesHeight(boolean selected)
1741   {
1742     showSeqFeaturesHeight = selected;
1743   }
1744
1745   public boolean isShowSequenceFeaturesHeight()
1746   {
1747     return showSeqFeaturesHeight;
1748   }
1749
1750   private boolean showAnnotation = true;
1751
1752   private boolean rightAlignIds = false;
1753
1754
1755   @Override
1756   public void setShowAnnotation(boolean b)
1757   {
1758     showAnnotation = b;
1759   }
1760
1761   @Override
1762   public boolean isShowAnnotation()
1763   {
1764     return showAnnotation;
1765   }
1766
1767   @Override
1768   public boolean isRightAlignIds()
1769   {
1770     return rightAlignIds;
1771   }
1772
1773   @Override
1774   public void setRightAlignIds(boolean rightAlignIds)
1775   {
1776     this.rightAlignIds = rightAlignIds;
1777   }
1778
1779 }