Merge branch 'JAL-1372_referenceseq' into feature_29/JAL-1372_referenceseq
[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   public void setColumnSelection(ColumnSelection colSel)
653   {
654     this.colSel = colSel;
655   }
656
657   /**
658    * 
659    * @return
660    */
661   @Override
662   public Map<SequenceI, SequenceCollectionI> getHiddenRepSequences()
663   {
664     return hiddenRepSequences;
665   }
666
667   @Override
668   public void setHiddenRepSequences(
669           Map<SequenceI, SequenceCollectionI> hiddenRepSequences)
670   {
671     this.hiddenRepSequences = hiddenRepSequences;
672   }
673
674   protected boolean hasHiddenColumns = false;
675
676   public void updateHiddenColumns()
677   {
678     hasHiddenColumns = colSel.getHiddenColumns() != null;
679   }
680
681   protected boolean hasHiddenRows = false;
682
683   public boolean hasHiddenRows()
684   {
685     return hasHiddenRows;
686   }
687
688   protected SequenceGroup selectionGroup;
689
690   public void setSequenceSetId(String newid)
691   {
692     if (sequenceSetID != null)
693     {
694       System.err
695               .println("Warning - overwriting a sequenceSetId for a viewport!");
696     }
697     sequenceSetID = new String(newid);
698   }
699
700   @Override
701   public String getSequenceSetId()
702   {
703     if (sequenceSetID == null)
704     {
705       sequenceSetID = alignment.hashCode() + "";
706     }
707
708     return sequenceSetID;
709   }
710
711   /**
712    * unique viewId for synchronizing state (e.g. with stored Jalview Project)
713    * 
714    */
715   protected String viewId = null;
716
717   public String getViewId()
718   {
719     if (viewId == null)
720     {
721       viewId = this.getSequenceSetId() + "." + this.hashCode() + "";
722     }
723     return viewId;
724   }
725
726   public void setIgnoreGapsConsensus(boolean b, AlignmentViewPanel ap)
727   {
728     ignoreGapsInConsensusCalculation = b;
729     if (ap != null)
730     {
731       updateConsensus(ap);
732       if (globalColourScheme != null)
733       {
734         globalColourScheme.setThreshold(globalColourScheme.getThreshold(),
735                 ignoreGapsInConsensusCalculation);
736       }
737     }
738
739   }
740
741   private long sgrouphash = -1, colselhash = -1;
742
743   /**
744    * checks current SelectionGroup against record of last hash value, and
745    * updates record.
746    * 
747    * @param b
748    *          update the record of last hash value
749    * 
750    * @return true if SelectionGroup changed since last call (when b is true)
751    */
752   public boolean isSelectionGroupChanged(boolean b)
753   {
754     int hc = (selectionGroup == null || selectionGroup.getSize() == 0) ? -1
755             : selectionGroup.hashCode();
756     if (hc != -1 && hc != sgrouphash)
757     {
758       if (b)
759       {
760         sgrouphash = hc;
761       }
762       return true;
763     }
764     return false;
765   }
766
767   /**
768    * checks current colsel against record of last hash value, and optionally
769    * updates record.
770    * 
771    * @param b
772    *          update the record of last hash value
773    * @return true if colsel changed since last call (when b is true)
774    */
775   public boolean isColSelChanged(boolean b)
776   {
777     int hc = (colSel == null || colSel.size() == 0) ? -1 : colSel
778             .hashCode();
779     if (hc != -1 && hc != colselhash)
780     {
781       if (b)
782       {
783         colselhash = hc;
784       }
785       return true;
786     }
787     return false;
788   }
789
790   @Override
791   public boolean getIgnoreGapsConsensus()
792   {
793     return ignoreGapsInConsensusCalculation;
794   }
795
796   // / property change stuff
797
798   // JBPNote Prolly only need this in the applet version.
799   private final java.beans.PropertyChangeSupport changeSupport = new java.beans.PropertyChangeSupport(
800           this);
801
802   protected boolean showConservation = true;
803
804   protected boolean showQuality = true;
805
806   protected boolean showConsensus = true;
807
808   Hashtable sequenceColours;
809
810   /**
811    * Property change listener for changes in alignment
812    * 
813    * @param listener
814    *          DOCUMENT ME!
815    */
816   public void addPropertyChangeListener(
817           java.beans.PropertyChangeListener listener)
818   {
819     changeSupport.addPropertyChangeListener(listener);
820   }
821
822   /**
823    * DOCUMENT ME!
824    * 
825    * @param listener
826    *          DOCUMENT ME!
827    */
828   public void removePropertyChangeListener(
829           java.beans.PropertyChangeListener listener)
830   {
831     changeSupport.removePropertyChangeListener(listener);
832   }
833
834   /**
835    * Property change listener for changes in alignment
836    * 
837    * @param prop
838    *          DOCUMENT ME!
839    * @param oldvalue
840    *          DOCUMENT ME!
841    * @param newvalue
842    *          DOCUMENT ME!
843    */
844   public void firePropertyChange(String prop, Object oldvalue,
845           Object newvalue)
846   {
847     changeSupport.firePropertyChange(prop, oldvalue, newvalue);
848   }
849
850   // common hide/show column stuff
851
852   public void hideSelectedColumns()
853   {
854     if (colSel.size() < 1)
855     {
856       return;
857     }
858
859     colSel.hideSelectedColumns();
860     setSelectionGroup(null);
861
862     hasHiddenColumns = true;
863   }
864
865   public void hideColumns(int start, int end)
866   {
867     if (start == end)
868     {
869       colSel.hideColumns(start);
870     }
871     else
872     {
873       colSel.hideColumns(start, end);
874     }
875
876     hasHiddenColumns = true;
877   }
878
879   public void showColumn(int col)
880   {
881     colSel.revealHiddenColumns(col);
882     if (colSel.getHiddenColumns() == null)
883     {
884       hasHiddenColumns = false;
885     }
886   }
887
888   public void showAllHiddenColumns()
889   {
890     colSel.revealAllHiddenColumns();
891     hasHiddenColumns = false;
892   }
893
894   // common hide/show seq stuff
895   public void showAllHiddenSeqs()
896   {
897     if (alignment.getHiddenSequences().getSize() > 0)
898     {
899       if (selectionGroup == null)
900       {
901         selectionGroup = new SequenceGroup();
902         selectionGroup.setEndRes(alignment.getWidth() - 1);
903       }
904       List<SequenceI> tmp = alignment.getHiddenSequences().showAll(
905               hiddenRepSequences);
906       for (SequenceI seq : tmp)
907       {
908         selectionGroup.addSequence(seq, false);
909         setSequenceAnnotationsVisible(seq, true);
910       }
911
912       hasHiddenRows = false;
913       hiddenRepSequences = null;
914
915       firePropertyChange("alignment", null, alignment.getSequences());
916       // used to set hasHiddenRows/hiddenRepSequences here, after the property
917       // changed event
918       sendSelection();
919     }
920   }
921
922   public void showSequence(int index)
923   {
924     List<SequenceI> tmp = alignment.getHiddenSequences().showSequence(
925             index,
926             hiddenRepSequences);
927     if (tmp.size() > 0)
928     {
929       if (selectionGroup == null)
930       {
931         selectionGroup = new SequenceGroup();
932         selectionGroup.setEndRes(alignment.getWidth() - 1);
933       }
934
935       for (SequenceI seq : tmp)
936       {
937         selectionGroup.addSequence(seq, false);
938         setSequenceAnnotationsVisible(seq, true);
939       }
940       // JBPNote: refactor: only update flag if we modified visiblity (used to
941       // do this regardless)
942       if (alignment.getHiddenSequences().getSize() < 1)
943       {
944         hasHiddenRows = false;
945       }
946       firePropertyChange("alignment", null, alignment.getSequences());
947       sendSelection();
948     }
949   }
950
951   public void hideAllSelectedSeqs()
952   {
953     if (selectionGroup == null || selectionGroup.getSize() < 1)
954     {
955       return;
956     }
957
958     SequenceI[] seqs = selectionGroup.getSequencesInOrder(alignment);
959
960     hideSequence(seqs);
961
962     setSelectionGroup(null);
963   }
964
965   public void hideSequence(SequenceI[] seq)
966   {
967     if (seq != null)
968     {
969       for (int i = 0; i < seq.length; i++)
970       {
971         alignment.getHiddenSequences().hideSequence(seq[i]);
972         setSequenceAnnotationsVisible(seq[i], false);
973       }
974       hasHiddenRows = true;
975       firePropertyChange("alignment", null, alignment.getSequences());
976     }
977   }
978
979   /**
980    * Set visibility for any annotations for the given sequence.
981    * 
982    * @param sequenceI
983    */
984   protected void setSequenceAnnotationsVisible(SequenceI sequenceI,
985           boolean visible)
986   {
987     for (AlignmentAnnotation ann : alignment.getAlignmentAnnotation())
988     {
989       if (ann.sequenceRef == sequenceI)
990       {
991         ann.visible = visible;
992       }
993     }
994   }
995
996   public void hideRepSequences(SequenceI repSequence, SequenceGroup sg)
997   {
998     int sSize = sg.getSize();
999     if (sSize < 2)
1000     {
1001       return;
1002     }
1003
1004     if (hiddenRepSequences == null)
1005     {
1006       hiddenRepSequences = new Hashtable();
1007     }
1008
1009     hiddenRepSequences.put(repSequence, sg);
1010
1011     // Hide all sequences except the repSequence
1012     SequenceI[] seqs = new SequenceI[sSize - 1];
1013     int index = 0;
1014     for (int i = 0; i < sSize; i++)
1015     {
1016       if (sg.getSequenceAt(i) != repSequence)
1017       {
1018         if (index == sSize - 1)
1019         {
1020           return;
1021         }
1022
1023         seqs[index++] = sg.getSequenceAt(i);
1024       }
1025     }
1026     sg.setSeqrep(repSequence); // note: not done in 2.7applet
1027     sg.setHidereps(true); // note: not done in 2.7applet
1028     hideSequence(seqs);
1029
1030   }
1031
1032   public boolean isHiddenRepSequence(SequenceI seq)
1033   {
1034     return alignment.getSeqrep()==seq || (hiddenRepSequences != null
1035             && hiddenRepSequences.containsKey(seq));
1036   }
1037
1038   public SequenceGroup getRepresentedSequences(SequenceI seq)
1039   {
1040     return (SequenceGroup) (hiddenRepSequences == null ? null
1041             : hiddenRepSequences.get(seq));
1042   }
1043
1044   public int adjustForHiddenSeqs(int alignmentIndex)
1045   {
1046     return alignment.getHiddenSequences().adjustForHiddenSeqs(
1047             alignmentIndex);
1048   }
1049
1050   // Selection manipulation
1051   /**
1052    * broadcast selection to any interested parties
1053    */
1054   public abstract void sendSelection();
1055
1056   public void invertColumnSelection()
1057   {
1058     colSel.invertColumnSelection(0, alignment.getWidth());
1059   }
1060
1061   /**
1062    * This method returns an array of new SequenceI objects derived from the
1063    * whole alignment or just the current selection with start and end points
1064    * adjusted
1065    * 
1066    * @note if you need references to the actual SequenceI objects in the
1067    *       alignment or currently selected then use getSequenceSelection()
1068    * @return selection as new sequenceI objects
1069    */
1070   public SequenceI[] getSelectionAsNewSequence()
1071   {
1072     SequenceI[] sequences;
1073     // JBPNote: Need to test jalviewLite.getSelectedSequencesAsAlignmentFrom -
1074     // this was the only caller in the applet for this method
1075     // JBPNote: in applet, this method returned references to the alignment
1076     // sequences, and it did not honour the presence/absence of annotation
1077     // attached to the alignment (probably!)
1078     if (selectionGroup == null || selectionGroup.getSize() == 0)
1079     {
1080       sequences = alignment.getSequencesArray();
1081       AlignmentAnnotation[] annots = alignment.getAlignmentAnnotation();
1082       for (int i = 0; i < sequences.length; i++)
1083       {
1084         sequences[i] = new Sequence(sequences[i], annots); // construct new
1085         // sequence with
1086         // subset of visible
1087         // annotation
1088       }
1089     }
1090     else
1091     {
1092       sequences = selectionGroup.getSelectionAsNewSequences(alignment);
1093     }
1094
1095     return sequences;
1096   }
1097
1098   /**
1099    * get the currently selected sequence objects or all the sequences in the
1100    * alignment.
1101    * 
1102    * @return array of references to sequence objects
1103    */
1104   @Override
1105   public SequenceI[] getSequenceSelection()
1106   {
1107     SequenceI[] sequences = null;
1108     if (selectionGroup != null)
1109     {
1110       sequences = selectionGroup.getSequencesInOrder(alignment);
1111     }
1112     if (sequences == null)
1113     {
1114       sequences = alignment.getSequencesArray();
1115     }
1116     return sequences;
1117   }
1118
1119   /**
1120    * This method returns the visible alignment as text, as seen on the GUI, ie
1121    * if columns are hidden they will not be returned in the result. Use this for
1122    * calculating trees, PCA, redundancy etc on views which contain hidden
1123    * columns.
1124    * 
1125    * @return String[]
1126    */
1127   @Override
1128   public jalview.datamodel.CigarArray getViewAsCigars(
1129           boolean selectedRegionOnly)
1130   {
1131     return new jalview.datamodel.CigarArray(alignment,
1132             (hasHiddenColumns ? colSel : null),
1133             (selectedRegionOnly ? selectionGroup : null));
1134   }
1135
1136   /**
1137    * return a compact representation of the current alignment selection to pass
1138    * to an analysis function
1139    * 
1140    * @param selectedOnly
1141    *          boolean true to just return the selected view
1142    * @return AlignmentView
1143    */
1144   @Override
1145   public jalview.datamodel.AlignmentView getAlignmentView(
1146           boolean selectedOnly)
1147   {
1148     return getAlignmentView(selectedOnly, false);
1149   }
1150
1151   /**
1152    * return a compact representation of the current alignment selection to pass
1153    * to an analysis function
1154    * 
1155    * @param selectedOnly
1156    *          boolean true to just return the selected view
1157    * @param markGroups
1158    *          boolean true to annotate the alignment view with groups on the
1159    *          alignment (and intersecting with selected region if selectedOnly
1160    *          is true)
1161    * @return AlignmentView
1162    */
1163   @Override
1164   public jalview.datamodel.AlignmentView getAlignmentView(
1165           boolean selectedOnly, boolean markGroups)
1166   {
1167     return new AlignmentView(alignment, colSel, selectionGroup,
1168             hasHiddenColumns, selectedOnly, markGroups);
1169   }
1170
1171   /**
1172    * This method returns the visible alignment as text, as seen on the GUI, ie
1173    * if columns are hidden they will not be returned in the result. Use this for
1174    * calculating trees, PCA, redundancy etc on views which contain hidden
1175    * columns.
1176    * 
1177    * @return String[]
1178    */
1179   @Override
1180   public String[] getViewAsString(boolean selectedRegionOnly)
1181   {
1182     String[] selection = null;
1183     SequenceI[] seqs = null;
1184     int i, iSize;
1185     int start = 0, end = 0;
1186     if (selectedRegionOnly && selectionGroup != null)
1187     {
1188       iSize = selectionGroup.getSize();
1189       seqs = selectionGroup.getSequencesInOrder(alignment);
1190       start = selectionGroup.getStartRes();
1191       end = selectionGroup.getEndRes() + 1;
1192     }
1193     else
1194     {
1195       iSize = alignment.getHeight();
1196       seqs = alignment.getSequencesArray();
1197       end = alignment.getWidth();
1198     }
1199
1200     selection = new String[iSize];
1201     if (hasHiddenColumns)
1202     {
1203       selection = colSel.getVisibleSequenceStrings(start, end, seqs);
1204     }
1205     else
1206     {
1207       for (i = 0; i < iSize; i++)
1208       {
1209         selection[i] = seqs[i].getSequenceAsString(start, end);
1210       }
1211
1212     }
1213     return selection;
1214   }
1215
1216   /**
1217    * return visible region boundaries within given column range
1218    * 
1219    * @param min
1220    *          first column (inclusive, from 0)
1221    * @param max
1222    *          last column (exclusive)
1223    * @return int[][] range of {start,end} visible positions
1224    */
1225   public int[][] getVisibleRegionBoundaries(int min, int max)
1226   {
1227     Vector regions = new Vector();
1228     int start = min;
1229     int end = max;
1230
1231     do
1232     {
1233       if (hasHiddenColumns)
1234       {
1235         if (start == 0)
1236         {
1237           start = colSel.adjustForHiddenColumns(start);
1238         }
1239
1240         end = colSel.getHiddenBoundaryRight(start);
1241         if (start == end)
1242         {
1243           end = max;
1244         }
1245         if (end > max)
1246         {
1247           end = max;
1248         }
1249       }
1250
1251       regions.addElement(new int[]
1252       { start, end });
1253
1254       if (hasHiddenColumns)
1255       {
1256         start = colSel.adjustForHiddenColumns(end);
1257         start = colSel.getHiddenBoundaryLeft(start) + 1;
1258       }
1259     } while (end < max);
1260
1261     int[][] startEnd = new int[regions.size()][2];
1262
1263     regions.copyInto(startEnd);
1264
1265     return startEnd;
1266
1267   }
1268
1269   @Override
1270   public List<AlignmentAnnotation> getVisibleAlignmentAnnotation(boolean selectedOnly)
1271   {
1272     ArrayList<AlignmentAnnotation> ala = new ArrayList<AlignmentAnnotation>();
1273     AlignmentAnnotation[] aa;
1274     if ((aa=alignment.getAlignmentAnnotation())!=null)
1275     {
1276       for (AlignmentAnnotation annot:aa)
1277       {
1278         AlignmentAnnotation clone = new AlignmentAnnotation(annot);
1279         if (selectedOnly && selectionGroup!=null)
1280         {
1281           colSel.makeVisibleAnnotation(selectionGroup.getStartRes(), selectionGroup.getEndRes(),clone);
1282         } else {
1283           colSel.makeVisibleAnnotation(clone);
1284         }
1285         ala.add(clone);
1286       }
1287     }
1288     return ala;
1289   }
1290
1291   /**
1292    * @return the padGaps
1293    */
1294   public boolean isPadGaps()
1295   {
1296     return padGaps;
1297   }
1298
1299   /**
1300    * @param padGaps
1301    *          the padGaps to set
1302    */
1303   public void setPadGaps(boolean padGaps)
1304   {
1305     this.padGaps = padGaps;
1306   }
1307
1308   /**
1309    * apply any post-edit constraints and trigger any calculations needed after
1310    * an edit has been performed on the alignment
1311    * 
1312    * @param ap
1313    */
1314   public void alignmentChanged(AlignmentViewPanel ap)
1315   {
1316     if (isPadGaps())
1317     {
1318       alignment.padGaps();
1319     }
1320     if (autoCalculateConsensus)
1321     {
1322       updateConsensus(ap);
1323     }
1324     if (hconsensus != null && autoCalculateConsensus)
1325     {
1326       updateConservation(ap);
1327     }
1328     if (autoCalculateStrucConsensus)
1329     {
1330       updateStrucConsensus(ap);
1331     }
1332
1333     // Reset endRes of groups if beyond alignment width
1334     int alWidth = alignment.getWidth();
1335     List<SequenceGroup> groups = alignment.getGroups();
1336     if (groups != null)
1337     {
1338       for (SequenceGroup sg : groups)
1339       {
1340         if (sg.getEndRes() > alWidth)
1341         {
1342           sg.setEndRes(alWidth - 1);
1343         }
1344       }
1345     }
1346
1347     if (selectionGroup != null && selectionGroup.getEndRes() > alWidth)
1348     {
1349       selectionGroup.setEndRes(alWidth - 1);
1350     }
1351
1352     resetAllColourSchemes();
1353     calculator.restartWorkers();
1354     // alignment.adjustSequenceAnnotations();
1355   }
1356
1357   /**
1358    * reset scope and do calculations for all applied colourschemes on alignment
1359    */
1360   void resetAllColourSchemes()
1361   {
1362     ColourSchemeI cs = globalColourScheme;
1363     if (cs != null)
1364     {
1365       cs.alignmentChanged(alignment, hiddenRepSequences);
1366
1367       cs.setConsensus(hconsensus);
1368       if (cs.conservationApplied())
1369       {
1370         cs.setConservation(Conservation.calculateConservation("All",
1371                 ResidueProperties.propHash, 3, alignment.getSequences(), 0,
1372                 alignment.getWidth(), false, getConsPercGaps(), false));
1373       }
1374     }
1375
1376     for (SequenceGroup sg : alignment.getGroups())
1377     {
1378       if (sg.cs != null)
1379       {
1380         sg.cs.alignmentChanged(sg, hiddenRepSequences);
1381       }
1382       sg.recalcConservation();
1383     }
1384   }
1385
1386   protected void initAutoAnnotation()
1387   {
1388     // TODO: add menu option action that nulls or creates consensus object
1389     // depending on if the user wants to see the annotation or not in a
1390     // specific alignment
1391
1392     if (hconsensus == null && !isDataset)
1393     {
1394       if (!alignment.isNucleotide())
1395       {
1396         initConservation();
1397         initQuality();
1398       }
1399       else
1400       {
1401         initRNAStructure();
1402       }
1403       initConsensus();
1404     }
1405   }
1406
1407   private void initConsensus()
1408   {
1409
1410     consensus = new AlignmentAnnotation("Consensus", "PID",
1411             new Annotation[1], 0f, 100f, AlignmentAnnotation.BAR_GRAPH);
1412     consensus.hasText = true;
1413     consensus.autoCalculated = true;
1414
1415     if (showConsensus)
1416     {
1417       alignment.addAnnotation(consensus);
1418     }
1419   }
1420
1421   private void initConservation()
1422   {
1423     if (showConservation)
1424     {
1425       if (conservation == null)
1426       {
1427         conservation = new AlignmentAnnotation("Conservation",
1428                 "Conservation of total alignment less than "
1429                         + getConsPercGaps() + "% gaps", new Annotation[1],
1430                 0f, 11f, AlignmentAnnotation.BAR_GRAPH);
1431         conservation.hasText = true;
1432         conservation.autoCalculated = true;
1433         alignment.addAnnotation(conservation);
1434       }
1435     }
1436   }
1437
1438   private void initQuality()
1439   {
1440     if (showQuality)
1441     {
1442       if (quality == null)
1443       {
1444         quality = new AlignmentAnnotation("Quality",
1445                 "Alignment Quality based on Blosum62 scores",
1446                 new Annotation[1], 0f, 11f, AlignmentAnnotation.BAR_GRAPH);
1447         quality.hasText = true;
1448         quality.autoCalculated = true;
1449         alignment.addAnnotation(quality);
1450       }
1451     }
1452   }
1453
1454   private void initRNAStructure()
1455   {
1456     if (alignment.hasRNAStructure() && strucConsensus == null)
1457     {
1458       strucConsensus = new AlignmentAnnotation("StrucConsensus", "PID",
1459               new Annotation[1], 0f, 100f, AlignmentAnnotation.BAR_GRAPH);
1460       strucConsensus.hasText = true;
1461       strucConsensus.autoCalculated = true;
1462
1463       if (showConsensus)
1464       {
1465         alignment.addAnnotation(strucConsensus);
1466       }
1467     }
1468   }
1469
1470   /*
1471    * (non-Javadoc)
1472    * 
1473    * @see jalview.api.AlignViewportI#calcPanelHeight()
1474    */
1475   public int calcPanelHeight()
1476   {
1477     // setHeight of panels
1478     AlignmentAnnotation[] aa = getAlignment().getAlignmentAnnotation();
1479     int height = 0;
1480     int charHeight = getCharHeight();
1481     if (aa != null)
1482     {
1483       BitSet graphgrp = new BitSet();
1484       for (int i = 0; i < aa.length; i++)
1485       {
1486         if (aa[i] == null)
1487         {
1488           System.err.println("Null annotation row: ignoring.");
1489           continue;
1490         }
1491         if (!aa[i].visible)
1492         {
1493           continue;
1494         }
1495         if (aa[i].graphGroup > -1)
1496         {
1497           if (graphgrp.get(aa[i].graphGroup))
1498           {
1499             continue;
1500           }
1501           else
1502           {
1503             graphgrp.set(aa[i].graphGroup);
1504           }
1505         }
1506         aa[i].height = 0;
1507
1508         if (aa[i].hasText)
1509         {
1510           aa[i].height += charHeight;
1511         }
1512
1513         if (aa[i].hasIcons)
1514         {
1515           aa[i].height += 16;
1516         }
1517
1518         if (aa[i].graph > 0)
1519         {
1520           aa[i].height += aa[i].graphHeight;
1521         }
1522
1523         if (aa[i].height == 0)
1524         {
1525           aa[i].height = 20;
1526         }
1527
1528         height += aa[i].height;
1529       }
1530     }
1531     if (height == 0)
1532     {
1533       // set minimum
1534       height = 20;
1535     }
1536     return height;
1537   }
1538
1539   @Override
1540   public void updateGroupAnnotationSettings(boolean applyGlobalSettings,
1541           boolean preserveNewGroupSettings)
1542   {
1543     boolean updateCalcs = false;
1544     boolean conv = isShowGroupConservation();
1545     boolean cons = isShowGroupConsensus();
1546     boolean showprf = isShowSequenceLogo();
1547     boolean showConsHist = isShowConsensusHistogram();
1548     boolean normLogo = isNormaliseSequenceLogo();
1549
1550     /**
1551      * TODO reorder the annotation rows according to group/sequence ordering on
1552      * alignment
1553      */
1554     boolean sortg = true;
1555
1556     // remove old automatic annotation
1557     // add any new annotation
1558
1559     // intersect alignment annotation with alignment groups
1560
1561     AlignmentAnnotation[] aan = alignment.getAlignmentAnnotation();
1562     List<SequenceGroup> oldrfs = new ArrayList<SequenceGroup>();
1563     if (aan != null)
1564     {
1565       for (int an = 0; an < aan.length; an++)
1566       {
1567         if (aan[an].autoCalculated && aan[an].groupRef != null)
1568         {
1569           oldrfs.add(aan[an].groupRef);
1570           alignment.deleteAnnotation(aan[an], false);
1571         }
1572       }
1573     }
1574     if (alignment.getGroups() != null)
1575     {
1576       for (SequenceGroup sg : alignment.getGroups())
1577       {
1578         updateCalcs = false;
1579         if (applyGlobalSettings
1580                 || (!preserveNewGroupSettings && !oldrfs.contains(sg)))
1581         {
1582           // set defaults for this group's conservation/consensus
1583           sg.setshowSequenceLogo(showprf);
1584           sg.setShowConsensusHistogram(showConsHist);
1585           sg.setNormaliseSequenceLogo(normLogo);
1586         }
1587         if (conv)
1588         {
1589           updateCalcs = true;
1590           alignment.addAnnotation(sg.getConservationRow(), 0);
1591         }
1592         if (cons)
1593         {
1594           updateCalcs = true;
1595           alignment.addAnnotation(sg.getConsensus(), 0);
1596         }
1597         // refresh the annotation rows
1598         if (updateCalcs)
1599         {
1600           sg.recalcConservation();
1601         }
1602       }
1603     }
1604     oldrfs.clear();
1605   }
1606   /**
1607    * show the reference sequence in the alignment view
1608    */
1609   private boolean displayReferenceSeq=false;
1610   /**
1611    * colour according to the reference sequence defined on the alignment
1612    */
1613   private boolean colourByReferenceSeq=false;
1614
1615   public boolean isDisplayReferenceSeq()
1616   {
1617     return alignment.hasSeqrep() && displayReferenceSeq;
1618   }
1619
1620   public void setDisplayReferenceSeq(boolean displayReferenceSeq)
1621   {
1622     this.displayReferenceSeq = displayReferenceSeq;
1623   }
1624
1625   public boolean isColourByReferenceSeq()
1626   {
1627     return alignment.hasSeqrep() && colourByReferenceSeq;
1628   }
1629
1630   public void setColourByReferenceSeq(boolean colourByReferenceSeq)
1631   {
1632     this.colourByReferenceSeq = colourByReferenceSeq;
1633   }
1634
1635   @Override
1636   public Color getSequenceColour(SequenceI seq)
1637   {
1638     Color sqc = Color.white;
1639     if (sequenceColours != null)
1640     {
1641       sqc = (Color) sequenceColours.get(seq);
1642       if (sqc == null)
1643       {
1644         sqc = Color.white;
1645       }
1646     }
1647     return sqc;
1648   }
1649
1650   @Override
1651   public void setSequenceColour(SequenceI seq, Color col)
1652   {
1653     if (sequenceColours == null)
1654     {
1655       sequenceColours = new Hashtable();
1656     }
1657
1658     if (col == null)
1659     {
1660       sequenceColours.remove(seq);
1661     }
1662     else
1663     {
1664       sequenceColours.put(seq, col);
1665     }
1666   }
1667
1668   @Override
1669   public void updateSequenceIdColours()
1670   {
1671     if (sequenceColours == null)
1672     {
1673       sequenceColours = new Hashtable();
1674     }
1675     for (SequenceGroup sg : alignment.getGroups())
1676     {
1677       if (sg.idColour != null)
1678       {
1679         for (SequenceI s : sg.getSequences(getHiddenRepSequences()))
1680         {
1681           sequenceColours.put(s, sg.idColour);
1682         }
1683       }
1684     }
1685   }
1686
1687   @Override
1688   public void clearSequenceColours()
1689   {
1690     sequenceColours = null;
1691   };
1692
1693   FeaturesDisplayedI featuresDisplayed = null;
1694
1695   @Override
1696   public FeaturesDisplayedI getFeaturesDisplayed()
1697   {
1698     return featuresDisplayed;
1699   }
1700
1701   public void setFeaturesDisplayed(FeaturesDisplayedI featuresDisplayedI)
1702   {
1703     featuresDisplayed = featuresDisplayedI;
1704   }
1705
1706   public boolean areFeaturesDisplayed()
1707   {
1708     return featuresDisplayed != null && featuresDisplayed.getRegisterdFeaturesCount()>0;
1709   }
1710
1711   /**
1712    * display setting for showing/hiding sequence features on alignment view
1713    */
1714   boolean showSequenceFeatures = false;
1715
1716   /**
1717    * set the flag
1718    * 
1719    * @param b
1720    *          features are displayed if true
1721    */
1722   @Override
1723   public void setShowSequenceFeatures(boolean b)
1724   {
1725     showSequenceFeatures = b;
1726   }
1727   @Override
1728   public boolean isShowSequenceFeatures()
1729   {
1730     return showSequenceFeatures;
1731   }
1732
1733   boolean showSeqFeaturesHeight;
1734
1735   public void setShowSequenceFeaturesHeight(boolean selected)
1736   {
1737     showSeqFeaturesHeight = selected;
1738   }
1739
1740   public boolean isShowSequenceFeaturesHeight()
1741   {
1742     return showSeqFeaturesHeight;
1743   }
1744
1745   private boolean showAnnotation = true;
1746
1747   private boolean rightAlignIds = false;
1748
1749   /**
1750    * DOCUMENT ME!
1751    * 
1752    * @return DOCUMENT ME!
1753    */
1754   public boolean getShowAnnotation()
1755   {
1756     return isShowAnnotation();
1757   }
1758
1759   /**
1760    * DOCUMENT ME!
1761    * 
1762    * @param b
1763    *          DOCUMENT ME!
1764    */
1765   public void setShowAnnotation(boolean b)
1766   {
1767     showAnnotation = b;
1768   }
1769
1770   public boolean isShowAnnotation()
1771   {
1772     return showAnnotation;
1773   }
1774
1775   public boolean isRightAlignIds()
1776   {
1777     return rightAlignIds;
1778   }
1779
1780   public void setRightAlignIds(boolean rightAlignIds)
1781   {
1782     this.rightAlignIds = rightAlignIds;
1783   }
1784
1785 }