9e9f1c1313dee9e0839d09146ccb44cc262f1380
[jalview.git] / src / jalview / gui / AlignmentPanel.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.9.0b1)
3  * Copyright (C) 2015 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.gui;
22
23 import jalview.analysis.AnnotationSorter;
24 import jalview.api.AlignViewportI;
25 import jalview.api.AlignmentViewPanel;
26 import jalview.bin.Cache;
27 import jalview.datamodel.AlignmentI;
28 import jalview.datamodel.SearchResults;
29 import jalview.datamodel.SequenceFeature;
30 import jalview.datamodel.SequenceGroup;
31 import jalview.datamodel.SequenceI;
32 import jalview.jbgui.GAlignmentPanel;
33 import jalview.math.AlignmentDimension;
34 import jalview.schemes.ResidueProperties;
35 import jalview.structure.StructureSelectionManager;
36 import jalview.util.MessageManager;
37 import jalview.util.Platform;
38
39 import java.awt.BorderLayout;
40 import java.awt.Color;
41 import java.awt.Container;
42 import java.awt.Dimension;
43 import java.awt.Font;
44 import java.awt.FontMetrics;
45 import java.awt.Graphics;
46 import java.awt.Insets;
47 import java.awt.event.AdjustmentEvent;
48 import java.awt.event.AdjustmentListener;
49 import java.awt.print.PageFormat;
50 import java.awt.print.Printable;
51 import java.awt.print.PrinterException;
52 import java.beans.PropertyChangeEvent;
53 import java.beans.PropertyChangeListener;
54 import java.io.File;
55 import java.io.FileWriter;
56 import java.io.PrintWriter;
57 import java.util.List;
58
59 import javax.swing.SwingUtilities;
60
61 /**
62  * DOCUMENT ME!
63  * 
64  * @author $author$
65  * @version $Revision: 1.161 $
66  */
67 public class AlignmentPanel extends GAlignmentPanel implements
68         AdjustmentListener, Printable, AlignmentViewPanel
69 {
70   public AlignViewport av;
71
72   OverviewPanel overviewPanel;
73
74   private SeqPanel seqPanel;
75
76   private IdPanel idPanel;
77
78   private boolean headless;
79
80   IdwidthAdjuster idwidthAdjuster;
81
82   /** DOCUMENT ME!! */
83   public AlignFrame alignFrame;
84
85   private ScalePanel scalePanel;
86
87   private AnnotationPanel annotationPanel;
88
89   private AnnotationLabels alabels;
90
91   // this value is set false when selection area being dragged
92   boolean fastPaint = true;
93
94   int hextent = 0;
95
96   int vextent = 0;
97
98   /*
99    * Flag set while scrolling to follow complementary cDNA/protein scroll. When
100    * true, suppresses invoking the same method recursively.
101    */
102   private boolean dontScrollComplement;
103
104   /**
105    * Creates a new AlignmentPanel object.
106    * 
107    * @param af
108    * @param av
109    */
110   public AlignmentPanel(AlignFrame af, final AlignViewport av)
111   {
112     alignFrame = af;
113     this.av = av;
114     setSeqPanel(new SeqPanel(av, this));
115     setIdPanel(new IdPanel(av, this));
116
117     setScalePanel(new ScalePanel(av, this));
118
119     idPanelHolder.add(getIdPanel(), BorderLayout.CENTER);
120     idwidthAdjuster = new IdwidthAdjuster(this);
121     idSpaceFillerPanel1.add(idwidthAdjuster, BorderLayout.CENTER);
122
123     setAnnotationPanel(new AnnotationPanel(this));
124     setAlabels(new AnnotationLabels(this));
125
126     annotationScroller.setViewportView(getAnnotationPanel());
127     annotationSpaceFillerHolder.add(getAlabels(), BorderLayout.CENTER);
128
129     scalePanelHolder.add(getScalePanel(), BorderLayout.CENTER);
130     seqPanelHolder.add(getSeqPanel(), BorderLayout.CENTER);
131
132     setScrollValues(0, 0);
133
134     hscroll.addAdjustmentListener(this);
135     vscroll.addAdjustmentListener(this);
136
137     final AlignmentPanel ap = this;
138     av.addPropertyChangeListener(new PropertyChangeListener()
139     {
140       public void propertyChange(PropertyChangeEvent evt)
141       {
142         if (evt.getPropertyName().equals("alignment"))
143         {
144           PaintRefresher.Refresh(ap, av.getSequenceSetId(), true, true);
145           alignmentChanged();
146         }
147       }
148     });
149     fontChanged();
150     adjustAnnotationHeight();
151     updateLayout();
152   }
153
154   @Override
155   public AlignViewportI getAlignViewport()
156   {
157     return av;
158   }
159
160   public void alignmentChanged()
161   {
162     av.alignmentChanged(this);
163
164     alignFrame.updateEditMenuBar();
165
166     paintAlignment(true);
167
168   }
169
170   /**
171    * DOCUMENT ME!
172    */
173   public void fontChanged()
174   {
175     // set idCanvas bufferedImage to null
176     // to prevent drawing old image
177     FontMetrics fm = getFontMetrics(av.getFont());
178
179     scalePanelHolder.setPreferredSize(new Dimension(10, av.getCharHeight()
180             + fm.getDescent()));
181     idSpaceFillerPanel1.setPreferredSize(new Dimension(10, av
182             .getCharHeight() + fm.getDescent()));
183
184     getIdPanel().getIdCanvas().gg = null;
185     getSeqPanel().seqCanvas.img = null;
186     getAnnotationPanel().adjustPanelHeight();
187
188     Dimension d = calculateIdWidth();
189
190     d.setSize(d.width + 4, d.height);
191     getIdPanel().getIdCanvas().setPreferredSize(d);
192     hscrollFillerPanel.setPreferredSize(d);
193
194     if (overviewPanel != null)
195     {
196       overviewPanel.setBoxPosition();
197     }
198     if (this.alignFrame.getSplitViewContainer() != null)
199     {
200       ((SplitFrame) this.alignFrame.getSplitViewContainer()).adjustLayout();
201     }
202
203     repaint();
204   }
205
206   /**
207    * Calculate the width of the alignment labels based on the displayed names
208    * and any bounds on label width set in preferences.
209    * 
210    * @return Dimension giving the maximum width of the alignment label panel
211    *         that should be used.
212    */
213   public Dimension calculateIdWidth()
214   {
215     // calculate sensible default width when no preference is available
216     Dimension r = null;
217     if (av.getIdWidth() < 0)
218     {
219       int afwidth = (alignFrame != null ? alignFrame.getWidth() : 300);
220       int maxwidth = Math.max(20, Math.min(afwidth - 200, 2 * afwidth / 3));
221       r = calculateIdWidth(maxwidth);
222       av.setIdWidth(r.width);
223     }
224     else
225     {
226       r = new Dimension();
227       r.width = av.getIdWidth();
228       r.height = 0;
229     }
230     return r;
231   }
232
233   /**
234    * Calculate the width of the alignment labels based on the displayed names
235    * and any bounds on label width set in preferences.
236    * 
237    * @param maxwidth
238    *          -1 or maximum width allowed for IdWidth
239    * @return Dimension giving the maximum width of the alignment label panel
240    *         that should be used.
241    */
242   public Dimension calculateIdWidth(int maxwidth)
243   {
244     Container c = new Container();
245
246     FontMetrics fm = c.getFontMetrics(new Font(av.font.getName(),
247             Font.ITALIC, av.font.getSize()));
248
249     AlignmentI al = av.getAlignment();
250     int i = 0;
251     int idWidth = 0;
252     String id;
253
254     while ((i < al.getHeight()) && (al.getSequenceAt(i) != null))
255     {
256       SequenceI s = al.getSequenceAt(i);
257
258       id = s.getDisplayId(av.getShowJVSuffix());
259
260       if (fm.stringWidth(id) > idWidth)
261       {
262         idWidth = fm.stringWidth(id);
263       }
264
265       i++;
266     }
267
268     // Also check annotation label widths
269     i = 0;
270
271     if (al.getAlignmentAnnotation() != null)
272     {
273       fm = c.getFontMetrics(getAlabels().getFont());
274
275       while (i < al.getAlignmentAnnotation().length)
276       {
277         String label = al.getAlignmentAnnotation()[i].label;
278
279         if (fm.stringWidth(label) > idWidth)
280         {
281           idWidth = fm.stringWidth(label);
282         }
283
284         i++;
285       }
286     }
287
288     return new Dimension(maxwidth < 0 ? idWidth : Math.min(maxwidth,
289             idWidth), 12);
290   }
291
292   /**
293    * Highlight the given results on the alignment.
294    * 
295    */
296   public void highlightSearchResults(SearchResults results)
297   {
298     scrollToPosition(results);
299     getSeqPanel().seqCanvas.highlightSearchResults(results);
300   }
301
302   /**
303    * Scroll the view to show the position of the highlighted region in results
304    * (if any) and redraw the overview
305    * 
306    * @param results
307    */
308   public boolean scrollToPosition(SearchResults results)
309   {
310     return scrollToPosition(results, 0, true, false);
311   }
312
313   /**
314    * Scroll the view to show the position of the highlighted region in results
315    * (if any)
316    * 
317    * @param searchResults
318    * @param redrawOverview
319    * @return
320    */
321   public boolean scrollToPosition(SearchResults searchResults,
322           boolean redrawOverview)
323   {
324     return scrollToPosition(searchResults, 0, redrawOverview, false);
325   }
326
327   /**
328    * Scroll the view to show the position of the highlighted region in results
329    * (if any)
330    * 
331    * @param results
332    * @param verticalOffset
333    *          if greater than zero, allows scrolling to a position below the
334    *          first displayed sequence
335    * @param redrawOverview
336    *          - when set, the overview will be recalculated (takes longer)
337    * @param centre
338    *          if true, try to centre the search results horizontally in the view
339    * @return false if results were not found
340    */
341   public boolean scrollToPosition(SearchResults results,
342           int verticalOffset, boolean redrawOverview, boolean centre)
343   {
344     int startv, endv, starts, ends;
345     // TODO: properly locate search results in view when large numbers of hidden
346     // columns exist before highlighted region
347     // do we need to scroll the panel?
348     // TODO: tons of nullpointerexceptions raised here.
349     if (results != null && results.getSize() > 0 && av != null
350             && av.getAlignment() != null)
351     {
352       int seqIndex = av.getAlignment().findIndex(results);
353       if (seqIndex == -1)
354       {
355         return false;
356       }
357       SequenceI seq = av.getAlignment().getSequenceAt(seqIndex);
358
359       int[] r = results.getResults(seq, 0, av.getAlignment().getWidth());
360       if (r == null)
361       {
362         return false;
363       }
364       int start = r[0];
365       int end = r[1];
366       // DEBUG
367       // System.err.println(this.av.viewName + " Seq : " + seqIndex
368       // + " Scroll to " + start + "," + end);
369
370       /*
371        * To centre results, scroll to positions half the visible width
372        * left/right of the start/end positions
373        */
374       if (centre)
375       {
376         int offset = (av.getEndRes() - av.getStartRes() + 1) / 2 - 1;
377         start = Math.max(start - offset, 0);
378         end = end + offset - 1;
379       }
380       if (start < 0)
381       {
382         return false;
383       }
384       if (end == seq.getEnd())
385       {
386         return false;
387       }
388       if (av.hasHiddenColumns())
389       {
390         start = av.getColumnSelection().findColumnPosition(start);
391         end = av.getColumnSelection().findColumnPosition(end);
392         if (start == end)
393         {
394           if (!av.getColumnSelection().isVisible(r[0]))
395           {
396             // don't scroll - position isn't visible
397             return false;
398           }
399         }
400       }
401
402       /*
403        * allow for offset of target sequence (actually scroll to one above it)
404        */
405       seqIndex = Math.max(0, seqIndex - verticalOffset);
406
407       // System.out.println("start=" + start + ", end=" + end + ", startv="
408       // + av.getStartRes() + ", endv=" + av.getEndRes() + ", starts="
409       // + av.getStartSeq() + ", ends=" + av.getEndSeq());
410       if (!av.getWrapAlignment())
411       {
412         if ((startv = av.getStartRes()) >= start)
413         {
414           /*
415            * Scroll left to make start of search results visible
416            */
417           // setScrollValues(start - 1, seqIndex); // plus one residue
418           setScrollValues(start, seqIndex);
419         }
420         else if ((endv = av.getEndRes()) <= end)
421         {
422           /*
423            * Scroll right to make end of search results visible
424            */
425           // setScrollValues(startv + 1 + end - endv, seqIndex); // plus one
426           setScrollValues(startv + end - endv, seqIndex);
427         }
428         else if ((starts = av.getStartSeq()) > seqIndex)
429         {
430           /*
431            * Scroll up to make start of search results visible
432            */
433           setScrollValues(av.getStartRes(), seqIndex);
434         }
435         else if ((ends = av.getEndSeq()) <= seqIndex)
436         {
437           /*
438            * Scroll down to make end of search results visible
439            */
440           setScrollValues(av.getStartRes(), starts + seqIndex - ends + 1);
441         }
442         /*
443          * Else results are already visible - no need to scroll
444          */
445       }
446       else
447       {
448         scrollToWrappedVisible(start);
449       }
450     }
451     if (redrawOverview && overviewPanel != null)
452     {
453       overviewPanel.setBoxPosition();
454     }
455     paintAlignment(redrawOverview);
456     return true;
457   }
458
459   void scrollToWrappedVisible(int res)
460   {
461     int cwidth = getSeqPanel().seqCanvas
462             .getWrappedCanvasWidth(getSeqPanel().seqCanvas.getWidth());
463     if (res < av.getStartRes() || res >= (av.getStartRes() + cwidth))
464     {
465       vscroll.setValue((res / cwidth));
466       av.startRes = vscroll.getValue() * cwidth;
467     }
468
469   }
470
471   /**
472    * DOCUMENT ME!
473    * 
474    * @return DOCUMENT ME!
475    */
476   public OverviewPanel getOverviewPanel()
477   {
478     return overviewPanel;
479   }
480
481   /**
482    * DOCUMENT ME!
483    * 
484    * @param op
485    *          DOCUMENT ME!
486    */
487   public void setOverviewPanel(OverviewPanel op)
488   {
489     overviewPanel = op;
490   }
491
492   /**
493    * 
494    * @param b
495    *          Hide or show annotation panel
496    * 
497    */
498   public void setAnnotationVisible(boolean b)
499   {
500     if (!av.getWrapAlignment())
501     {
502       annotationSpaceFillerHolder.setVisible(b);
503       annotationScroller.setVisible(b);
504     }
505     repaint();
506   }
507
508   /**
509    * automatically adjust annotation panel height for new annotation whilst
510    * ensuring the alignment is still visible.
511    */
512   public void adjustAnnotationHeight()
513   {
514     // TODO: display vertical annotation scrollbar if necessary
515     // this is called after loading new annotation onto alignment
516     if (alignFrame.getHeight() == 0)
517     {
518       System.out.println("NEEDS FIXING");
519     }
520     validateAnnotationDimensions(true);
521     addNotify();
522     paintAlignment(true);
523   }
524
525   /**
526    * calculate the annotation dimensions and refresh slider values accordingly.
527    * need to do repaints/notifys afterwards.
528    */
529   protected void validateAnnotationDimensions(boolean adjustPanelHeight)
530   {
531     int annotationHeight = getAnnotationPanel().adjustPanelHeight();
532
533     if (adjustPanelHeight)
534     {
535       int rowHeight = av.getCharHeight();
536       int alignmentHeight = rowHeight * av.getAlignment().getHeight();
537
538       /*
539        * Estimate available height in the AlignFrame for alignment +
540        * annotations. Deduct an estimate for title bar, menu bar, scale panel,
541        * hscroll, status bar (as these are not laid out we can't inspect their
542        * actual heights). Insets gives frame borders.
543        */
544       int stuff = Platform.isAMac() ? 80 : 100;
545       Insets insets = alignFrame.getInsets();
546       int availableHeight = alignFrame.getHeight() - stuff - insets.top
547               - insets.bottom;
548
549       /*
550        * If not enough vertical space, maximize annotation height while keeping
551        * at least two rows of alignment visible
552        */
553       if (annotationHeight + alignmentHeight > availableHeight)
554       {
555         annotationHeight = Math.min(annotationHeight, availableHeight - 2
556                 * rowHeight);
557       }
558     }
559     else
560     {
561       // maintain same window layout whilst updating sliders
562       annotationHeight = annotationScroller.getSize().height;
563     }
564     hscroll.addNotify();
565
566     annotationScroller.setPreferredSize(new Dimension(annotationScroller
567             .getWidth(), annotationHeight));
568
569     Dimension e = idPanel.getSize();
570     alabels.setSize(new Dimension(e.width, annotationHeight));
571
572     annotationSpaceFillerHolder.setPreferredSize(new Dimension(
573             annotationSpaceFillerHolder.getWidth(), annotationHeight));
574     annotationScroller.validate();
575     annotationScroller.addNotify();
576   }
577
578   /**
579    * update alignment layout for viewport settings
580    * 
581    * @param wrap
582    *          DOCUMENT ME!
583    */
584   public void updateLayout()
585   {
586     fontChanged();
587     setAnnotationVisible(av.isShowAnnotation());
588     boolean wrap = av.getWrapAlignment();
589     av.startSeq = 0;
590     scalePanelHolder.setVisible(!wrap);
591     hscroll.setVisible(!wrap);
592     idwidthAdjuster.setVisible(!wrap);
593
594     if (wrap)
595     {
596       annotationScroller.setVisible(false);
597       annotationSpaceFillerHolder.setVisible(false);
598     }
599     else if (av.isShowAnnotation())
600     {
601       annotationScroller.setVisible(true);
602       annotationSpaceFillerHolder.setVisible(true);
603     }
604
605     idSpaceFillerPanel1.setVisible(!wrap);
606
607     repaint();
608   }
609
610   // return value is true if the scroll is valid
611   public boolean scrollUp(boolean up)
612   {
613     if (up)
614     {
615       if (vscroll.getValue() < 1)
616       {
617         return false;
618       }
619
620       fastPaint = false;
621       vscroll.setValue(vscroll.getValue() - 1);
622     }
623     else
624     {
625       if ((vextent + vscroll.getValue()) >= av.getAlignment().getHeight())
626       {
627         return false;
628       }
629
630       fastPaint = false;
631       vscroll.setValue(vscroll.getValue() + 1);
632     }
633
634     fastPaint = true;
635
636     return true;
637   }
638
639   /**
640    * DOCUMENT ME!
641    * 
642    * @param right
643    *          DOCUMENT ME!
644    * 
645    * @return DOCUMENT ME!
646    */
647   public boolean scrollRight(boolean right)
648   {
649     if (!right)
650     {
651       if (hscroll.getValue() < 1)
652       {
653         return false;
654       }
655
656       fastPaint = false;
657       hscroll.setValue(hscroll.getValue() - 1);
658     }
659     else
660     {
661       if ((hextent + hscroll.getValue()) >= av.getAlignment().getWidth())
662       {
663         return false;
664       }
665
666       fastPaint = false;
667       hscroll.setValue(hscroll.getValue() + 1);
668     }
669
670     fastPaint = true;
671
672     return true;
673   }
674
675   /**
676    * Adjust row/column scrollers to show a visible position in the alignment.
677    * 
678    * @param x
679    *          visible column to scroll to
680    * @param y
681    *          visible row to scroll to
682    * 
683    */
684   public void setScrollValues(int x, int y)
685   {
686     // System.err.println("Scroll " + this.av.viewName + " to " + x + "," + y);
687     if (av == null || av.getAlignment() == null)
688     {
689       return;
690     }
691     int width = av.getAlignment().getWidth();
692     int height = av.getAlignment().getHeight();
693
694     if (av.hasHiddenColumns())
695     {
696       width = av.getColumnSelection().findColumnPosition(width);
697     }
698
699     av.setEndRes((x + (getSeqPanel().seqCanvas.getWidth() / av
700             .getCharWidth())) - 1);
701
702     hextent = getSeqPanel().seqCanvas.getWidth() / av.getCharWidth();
703     vextent = getSeqPanel().seqCanvas.getHeight() / av.getCharHeight();
704
705     if (hextent > width)
706     {
707       hextent = width;
708     }
709
710     if (vextent > height)
711     {
712       vextent = height;
713     }
714
715     if ((hextent + x) > width)
716     {
717       x = width - hextent;
718     }
719
720     if ((vextent + y) > height)
721     {
722       y = height - vextent;
723     }
724
725     if (y < 0)
726     {
727       y = 0;
728     }
729
730     if (x < 0)
731     {
732       x = 0;
733     }
734
735     /*
736      * each scroll adjustment triggers adjustmentValueChanged, which resets the
737      * 'do not scroll complement' flag; ensure it is the same for both
738      * operations
739      */
740     boolean flag = isDontScrollComplement();
741     hscroll.setValues(x, hextent, 0, width);
742     setDontScrollComplement(flag);
743     vscroll.setValues(y, vextent, 0, height);
744   }
745
746   /**
747    * DOCUMENT ME!
748    * 
749    * @param evt
750    *          DOCUMENT ME!
751    */
752   public void adjustmentValueChanged(AdjustmentEvent evt)
753   {
754     int oldX = av.getStartRes();
755     int oldY = av.getStartSeq();
756
757     if (evt.getSource() == hscroll)
758     {
759       int x = hscroll.getValue();
760       av.setStartRes(x);
761       av.setEndRes((x + (getSeqPanel().seqCanvas.getWidth() / av
762               .getCharWidth())) - 1);
763     }
764
765     if (evt.getSource() == vscroll)
766     {
767       int offy = vscroll.getValue();
768
769       if (av.getWrapAlignment())
770       {
771         if (offy > -1)
772         {
773           int rowSize = getSeqPanel().seqCanvas
774                   .getWrappedCanvasWidth(getSeqPanel().seqCanvas.getWidth());
775           av.setStartRes(offy * rowSize);
776           av.setEndRes((offy + 1) * rowSize);
777         }
778         else
779         {
780           // This is only called if file loaded is a jar file that
781           // was wrapped when saved and user has wrap alignment true
782           // as preference setting
783           SwingUtilities.invokeLater(new Runnable()
784           {
785             public void run()
786             {
787               setScrollValues(av.getStartRes(), av.getStartSeq());
788             }
789           });
790         }
791       }
792       else
793       {
794         av.setStartSeq(offy);
795         av.setEndSeq(offy
796                 + (getSeqPanel().seqCanvas.getHeight() / av.getCharHeight()));
797       }
798     }
799
800     if (overviewPanel != null)
801     {
802       overviewPanel.setBoxPosition();
803     }
804
805     int scrollX = av.startRes - oldX;
806     int scrollY = av.startSeq - oldY;
807
808     if (av.getWrapAlignment() || !fastPaint)
809     {
810       repaint();
811     }
812     else
813     {
814       // Make sure we're not trying to draw a panel
815       // larger than the visible window
816       if (scrollX > av.endRes - av.startRes)
817       {
818         scrollX = av.endRes - av.startRes;
819       }
820       else if (scrollX < av.startRes - av.endRes)
821       {
822         scrollX = av.startRes - av.endRes;
823       }
824
825       if (scrollX != 0 || scrollY != 0)
826       {
827         getIdPanel().getIdCanvas().fastPaint(scrollY);
828         getSeqPanel().seqCanvas.fastPaint(scrollX, scrollY);
829         getScalePanel().repaint();
830
831         if (av.isShowAnnotation() && scrollX != 0)
832         {
833           getAnnotationPanel().fastPaint(scrollX);
834         }
835       }
836     }
837     /*
838      * If there is one, scroll the (Protein/cDNA) complementary alignment to
839      * match, unless we are ourselves doing that.
840      */
841     if (isDontScrollComplement())
842     {
843       setDontScrollComplement(false);
844     }
845     else
846     {
847       av.scrollComplementaryAlignment();
848     }
849   }
850
851   /**
852    * Repaint the alignment including the annotations and overview panels (if
853    * shown).
854    */
855   public void paintAlignment(boolean updateOverview)
856   {
857     final AnnotationSorter sorter = new AnnotationSorter(getAlignment(),
858             av.isShowAutocalculatedAbove());
859     sorter.sort(getAlignment().getAlignmentAnnotation(),
860             av.getSortAnnotationsBy());
861     repaint();
862
863     if (updateOverview)
864     {
865       // TODO: determine if this paintAlignment changed structure colours
866       av.getStructureSelectionManager().sequenceColoursChanged(this);
867
868       if (overviewPanel != null)
869       {
870         overviewPanel.updateOverviewImage();
871       }
872     }
873   }
874
875   /**
876    * DOCUMENT ME!
877    * 
878    * @param g
879    *          DOCUMENT ME!
880    */
881   public void paintComponent(Graphics g)
882   {
883     invalidate();
884
885     Dimension d = getIdPanel().getIdCanvas().getPreferredSize();
886     idPanelHolder.setPreferredSize(d);
887     hscrollFillerPanel.setPreferredSize(new Dimension(d.width, 12));
888     validate();
889
890     /*
891      * set scroll bar positions; first suppress this being 'followed' in any
892      * complementary split pane
893      */
894     setDontScrollComplement(true);
895
896     if (av.getWrapAlignment())
897     {
898       int maxwidth = av.getAlignment().getWidth();
899
900       if (av.hasHiddenColumns())
901       {
902         maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
903       }
904
905       int canvasWidth = getSeqPanel().seqCanvas
906               .getWrappedCanvasWidth(getSeqPanel().seqCanvas.getWidth());
907       if (canvasWidth > 0)
908       {
909         int max = maxwidth
910                 / getSeqPanel().seqCanvas
911                         .getWrappedCanvasWidth(getSeqPanel().seqCanvas
912                                 .getWidth()) + 1;
913         vscroll.setMaximum(max);
914         vscroll.setUnitIncrement(1);
915         vscroll.setVisibleAmount(1);
916       }
917     }
918     else
919     {
920       setScrollValues(av.getStartRes(), av.getStartSeq());
921     }
922   }
923
924   /**
925    * DOCUMENT ME!
926    * 
927    * @param pg
928    *          DOCUMENT ME!
929    * @param pf
930    *          DOCUMENT ME!
931    * @param pi
932    *          DOCUMENT ME!
933    * 
934    * @return DOCUMENT ME!
935    * 
936    * @throws PrinterException
937    *           DOCUMENT ME!
938    */
939   public int print(Graphics pg, PageFormat pf, int pi)
940           throws PrinterException
941   {
942     pg.translate((int) pf.getImageableX(), (int) pf.getImageableY());
943
944     int pwidth = (int) pf.getImageableWidth();
945     int pheight = (int) pf.getImageableHeight();
946
947     if (av.getWrapAlignment())
948     {
949       return printWrappedAlignment(pg, pwidth, pheight, pi);
950     }
951     else
952     {
953       return printUnwrapped(pg, pwidth, pheight, pi);
954     }
955   }
956
957   /**
958    * DOCUMENT ME!
959    * 
960    * @param pg
961    *          DOCUMENT ME!
962    * @param pwidth
963    *          DOCUMENT ME!
964    * @param pheight
965    *          DOCUMENT ME!
966    * @param pi
967    *          DOCUMENT ME!
968    * 
969    * @return DOCUMENT ME!
970    * 
971    * @throws PrinterException
972    *           DOCUMENT ME!
973    */
974   public int printUnwrapped(Graphics pg, int pwidth, int pheight, int pi)
975           throws PrinterException
976   {
977     int idWidth = getVisibleIdWidth(false);
978     FontMetrics fm = getFontMetrics(av.getFont());
979     int scaleHeight = av.getCharHeight() + fm.getDescent();
980
981     pg.setColor(Color.white);
982     pg.fillRect(0, 0, pwidth, pheight);
983     pg.setFont(av.getFont());
984
985     // //////////////////////////////////
986     // / How many sequences and residues can we fit on a printable page?
987     int totalRes = (pwidth - idWidth) / av.getCharWidth();
988
989     int totalSeq = (pheight - scaleHeight) / av.getCharHeight() - 1;
990
991     int pagesWide = (av.getAlignment().getWidth() / totalRes) + 1;
992
993     // ///////////////////////////
994     // / Only print these sequences and residues on this page
995     int startRes;
996
997     // ///////////////////////////
998     // / Only print these sequences and residues on this page
999     int endRes;
1000
1001     // ///////////////////////////
1002     // / Only print these sequences and residues on this page
1003     int startSeq;
1004
1005     // ///////////////////////////
1006     // / Only print these sequences and residues on this page
1007     int endSeq;
1008     startRes = (pi % pagesWide) * totalRes;
1009     endRes = (startRes + totalRes) - 1;
1010
1011     if (endRes > (av.getAlignment().getWidth() - 1))
1012     {
1013       endRes = av.getAlignment().getWidth() - 1;
1014     }
1015
1016     startSeq = (pi / pagesWide) * totalSeq;
1017     endSeq = startSeq + totalSeq;
1018
1019     if (endSeq > av.getAlignment().getHeight())
1020     {
1021       endSeq = av.getAlignment().getHeight();
1022     }
1023
1024     int pagesHigh = ((av.getAlignment().getHeight() / totalSeq) + 1)
1025             * pheight;
1026
1027     if (av.isShowAnnotation())
1028     {
1029       pagesHigh += getAnnotationPanel().adjustPanelHeight() + 3;
1030     }
1031
1032     pagesHigh /= pheight;
1033
1034     if (pi >= (pagesWide * pagesHigh))
1035     {
1036       return Printable.NO_SUCH_PAGE;
1037     }
1038
1039     // draw Scale
1040     pg.translate(idWidth, 0);
1041     getScalePanel().drawScale(pg, startRes, endRes, pwidth - idWidth,
1042             scaleHeight);
1043     pg.translate(-idWidth, scaleHeight);
1044
1045     // //////////////
1046     // Draw the ids
1047     Color currentColor = null;
1048     Color currentTextColor = null;
1049
1050     pg.setFont(getIdPanel().getIdCanvas().getIdfont());
1051
1052     SequenceI seq;
1053     for (int i = startSeq; i < endSeq; i++)
1054     {
1055       seq = av.getAlignment().getSequenceAt(i);
1056       if ((av.getSelectionGroup() != null)
1057               && av.getSelectionGroup().getSequences(null).contains(seq))
1058       {
1059         currentColor = Color.gray;
1060         currentTextColor = Color.black;
1061       }
1062       else
1063       {
1064         currentColor = av.getSequenceColour(seq);
1065         currentTextColor = Color.black;
1066       }
1067
1068       pg.setColor(currentColor);
1069       pg.fillRect(0, (i - startSeq) * av.getCharHeight(), idWidth,
1070               av.getCharHeight());
1071
1072       pg.setColor(currentTextColor);
1073
1074       int xPos = 0;
1075       if (av.isRightAlignIds())
1076       {
1077         fm = pg.getFontMetrics();
1078         xPos = idWidth
1079                 - fm.stringWidth(seq.getDisplayId(av.getShowJVSuffix()))
1080                 - 4;
1081       }
1082
1083       pg.drawString(seq.getDisplayId(av.getShowJVSuffix()), xPos,
1084               (((i - startSeq) * av.getCharHeight()) + av.getCharHeight())
1085                       - (av.getCharHeight() / 5));
1086     }
1087
1088     pg.setFont(av.getFont());
1089
1090     // draw main sequence panel
1091     pg.translate(idWidth, 0);
1092     getSeqPanel().seqCanvas.drawPanel(pg, startRes, endRes, startSeq,
1093             endSeq, 0);
1094
1095     if (av.isShowAnnotation() && (endSeq == av.getAlignment().getHeight()))
1096     {
1097       // draw annotation - need to offset for current scroll position
1098       int offset = -getAlabels().getScrollOffset();
1099       pg.translate(0, offset);
1100       pg.translate(-idWidth - 3, (endSeq - startSeq) * av.getCharHeight()
1101               + 3);
1102       getAlabels().drawComponent(pg, idWidth);
1103       pg.translate(idWidth + 3, 0);
1104       getAnnotationPanel().renderer.drawComponent(getAnnotationPanel(), av,
1105               pg, -1, startRes, endRes + 1);
1106       pg.translate(0, -offset);
1107     }
1108
1109     return Printable.PAGE_EXISTS;
1110   }
1111
1112   /**
1113    * DOCUMENT ME!
1114    * 
1115    * @param pg
1116    *          DOCUMENT ME!
1117    * @param pwidth
1118    *          DOCUMENT ME!
1119    * @param pheight
1120    *          DOCUMENT ME!
1121    * @param pi
1122    *          DOCUMENT ME!
1123    * 
1124    * @return DOCUMENT ME!
1125    * 
1126    * @throws PrinterException
1127    *           DOCUMENT ME!
1128    */
1129   public int printWrappedAlignment(Graphics pg, int pwidth, int pheight,
1130           int pi) throws PrinterException
1131   {
1132     int annotationHeight = 0;
1133     AnnotationLabels labels = null;
1134     if (av.isShowAnnotation())
1135     {
1136       annotationHeight = getAnnotationPanel().adjustPanelHeight();
1137       labels = new AnnotationLabels(av);
1138     }
1139
1140     int hgap = av.getCharHeight();
1141     if (av.getScaleAboveWrapped())
1142     {
1143       hgap += av.getCharHeight();
1144     }
1145
1146     int cHeight = av.getAlignment().getHeight() * av.getCharHeight() + hgap
1147             + annotationHeight;
1148
1149     int idWidth = getVisibleIdWidth(false);
1150
1151     int maxwidth = av.getAlignment().getWidth();
1152     if (av.hasHiddenColumns())
1153     {
1154       maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
1155     }
1156
1157     int resWidth = getSeqPanel().seqCanvas.getWrappedCanvasWidth(pwidth
1158             - idWidth);
1159
1160     int totalHeight = cHeight * (maxwidth / resWidth + 1);
1161
1162     pg.setColor(Color.white);
1163     pg.fillRect(0, 0, pwidth, pheight);
1164     pg.setFont(av.getFont());
1165
1166     // //////////////
1167     // Draw the ids
1168     pg.setColor(Color.black);
1169
1170     pg.translate(0, -pi * pheight);
1171
1172     pg.setClip(0, pi * pheight, pwidth, pheight);
1173
1174     int ypos = hgap;
1175
1176     do
1177     {
1178       for (int i = 0; i < av.getAlignment().getHeight(); i++)
1179       {
1180         pg.setFont(getIdPanel().getIdCanvas().getIdfont());
1181         SequenceI s = av.getAlignment().getSequenceAt(i);
1182         String string = s.getDisplayId(av.getShowJVSuffix());
1183         int xPos = 0;
1184         if (av.isRightAlignIds())
1185         {
1186           FontMetrics fm = pg.getFontMetrics();
1187           xPos = idWidth - fm.stringWidth(string) - 4;
1188         }
1189         pg.drawString(string, xPos,
1190                 ((i * av.getCharHeight()) + ypos + av.getCharHeight())
1191                         - (av.getCharHeight() / 5));
1192       }
1193       if (labels != null)
1194       {
1195         pg.translate(-3,
1196                 ypos + (av.getAlignment().getHeight() * av.getCharHeight()));
1197
1198         pg.setFont(av.getFont());
1199         labels.drawComponent(pg, idWidth);
1200         pg.translate(
1201                 +3,
1202                 -ypos
1203                         - (av.getAlignment().getHeight() * av
1204                                 .getCharHeight()));
1205       }
1206
1207       ypos += cHeight;
1208     } while (ypos < totalHeight);
1209
1210     pg.translate(idWidth, 0);
1211
1212     getSeqPanel().seqCanvas.drawWrappedPanel(pg, pwidth - idWidth,
1213             totalHeight, 0);
1214
1215     if ((pi * pheight) < totalHeight)
1216     {
1217       return Printable.PAGE_EXISTS;
1218
1219     }
1220     else
1221     {
1222       return Printable.NO_SUCH_PAGE;
1223     }
1224   }
1225
1226   /**
1227    * get current sequence ID panel width, or nominal value if panel were to be
1228    * displayed using default settings
1229    * 
1230    * @return
1231    */
1232   public int getVisibleIdWidth()
1233   {
1234     return getVisibleIdWidth(true);
1235   }
1236
1237   /**
1238    * get current sequence ID panel width, or nominal value if panel were to be
1239    * displayed using default settings
1240    * 
1241    * @param onscreen
1242    *          indicate if the Id width for onscreen or offscreen display should
1243    *          be returned
1244    * @return
1245    */
1246   public int getVisibleIdWidth(boolean onscreen)
1247   {
1248     // see if rendering offscreen - check preferences and calc width accordingly
1249     if (!onscreen && Cache.getDefault("FIGURE_AUTOIDWIDTH", false))
1250     {
1251       return calculateIdWidth(-1).width + 4;
1252     }
1253     Integer idwidth = null;
1254     if (onscreen
1255             || (idwidth = Cache.getIntegerProperty("FIGURE_FIXEDIDWIDTH")) == null)
1256     {
1257       return (getIdPanel().getWidth() > 0 ? getIdPanel().getWidth()
1258               : calculateIdWidth().width + 4);
1259     }
1260     return idwidth.intValue() + 4;
1261   }
1262
1263   void makeAlignmentImage(jalview.util.ImageMaker.TYPE type, File file)
1264   {
1265     long progress = System.currentTimeMillis();
1266     headless = (System.getProperty("java.awt.headless") != null && System
1267             .getProperty("java.awt.headless").equals("true"));
1268     if (alignFrame != null && !headless)
1269     {
1270       alignFrame.setProgressBar(MessageManager.formatMessage(
1271               "status.saving_file", new Object[] { type.getLabel() }),
1272               progress);
1273     }
1274     try
1275     {
1276       AlignmentDimension aDimension = getAlignmentDimension();
1277       try
1278       {
1279         jalview.util.ImageMaker im;
1280         final String imageAction, imageTitle;
1281         if (type == jalview.util.ImageMaker.TYPE.PNG)
1282         {
1283           imageAction = "Create PNG image from alignment";
1284           imageTitle = null;
1285         }
1286         else if (type == jalview.util.ImageMaker.TYPE.EPS)
1287         {
1288           imageAction = "Create EPS file from alignment";
1289           imageTitle = alignFrame.getTitle();
1290         }
1291         else
1292         {
1293           imageAction = "Create SVG file from alignment";
1294           imageTitle = alignFrame.getTitle();
1295         }
1296
1297         im = new jalview.util.ImageMaker(this, type, imageAction,
1298                 aDimension.getWidth(), aDimension.getHeight(), file,
1299                 imageTitle);
1300         if (av.getWrapAlignment())
1301         {
1302           if (im.getGraphics() != null)
1303           {
1304             printWrappedAlignment(im.getGraphics(), aDimension.getWidth(),
1305                     aDimension.getHeight(), 0);
1306             im.writeImage();
1307           }
1308         }
1309         else
1310         {
1311           if (im.getGraphics() != null)
1312           {
1313             printUnwrapped(im.getGraphics(), aDimension.getWidth(),
1314                     aDimension.getHeight(), 0);
1315             im.writeImage();
1316           }
1317         }
1318       } catch (OutOfMemoryError err)
1319       {
1320         // Be noisy here.
1321         System.out.println("########################\n" + "OUT OF MEMORY "
1322                 + file + "\n" + "########################");
1323         new OOMWarning("Creating Image for " + file, err);
1324         // System.out.println("Create IMAGE: " + err);
1325       } catch (Exception ex)
1326       {
1327         ex.printStackTrace();
1328       }
1329     } finally
1330     {
1331       if (alignFrame != null && !headless)
1332       {
1333         alignFrame.setProgressBar(
1334                 MessageManager.getString("status.export_complete"),
1335                 progress);
1336       }
1337     }
1338   }
1339
1340   public AlignmentDimension getAlignmentDimension()
1341   {
1342     int maxwidth = av.getAlignment().getWidth();
1343     if (av.hasHiddenColumns())
1344     {
1345       maxwidth = av.getColumnSelection().findColumnPosition(maxwidth);
1346     }
1347
1348     int height = ((av.getAlignment().getHeight() + 1) * av.getCharHeight())
1349             + getScalePanel().getHeight();
1350     int width = getVisibleIdWidth(false) + (maxwidth * av.getCharWidth());
1351
1352     if (av.getWrapAlignment())
1353     {
1354       height = getWrappedHeight();
1355       if (headless)
1356       {
1357         // need to obtain default alignment width and then add in any
1358         // additional allowance for id margin
1359         // this duplicates the calculation in getWrappedHeight but adjusts for
1360         // offscreen idWith
1361         width = alignFrame.getWidth() - vscroll.getPreferredSize().width
1362                 - alignFrame.getInsets().left
1363                 - alignFrame.getInsets().right - getVisibleIdWidth()
1364                 + getVisibleIdWidth(false);
1365       }
1366       else
1367       {
1368         width = getSeqPanel().getWidth() + getVisibleIdWidth(false);
1369       }
1370
1371     }
1372     else if (av.isShowAnnotation())
1373     {
1374       height += getAnnotationPanel().adjustPanelHeight() + 3;
1375     }
1376     return new AlignmentDimension(width, height);
1377
1378   }
1379
1380   /**
1381    * DOCUMENT ME!
1382    */
1383   public void makeEPS(File epsFile)
1384   {
1385     makeAlignmentImage(jalview.util.ImageMaker.TYPE.EPS, epsFile);
1386   }
1387
1388   /**
1389    * DOCUMENT ME!
1390    */
1391   public void makePNG(File pngFile)
1392   {
1393     makeAlignmentImage(jalview.util.ImageMaker.TYPE.PNG, pngFile);
1394   }
1395
1396   public void makeSVG(File svgFile)
1397   {
1398     makeAlignmentImage(jalview.util.ImageMaker.TYPE.SVG, svgFile);
1399   }
1400
1401   public void makePNGImageMap(File imgMapFile, String imageName)
1402   {
1403     // /////ONLY WORKS WITH NONE WRAPPED ALIGNMENTS
1404     // ////////////////////////////////////////////
1405     int idWidth = getVisibleIdWidth(false);
1406     FontMetrics fm = getFontMetrics(av.getFont());
1407     int scaleHeight = av.getCharHeight() + fm.getDescent();
1408
1409     // Gen image map
1410     // ////////////////////////////////
1411     if (imgMapFile != null)
1412     {
1413       try
1414       {
1415         int s, sSize = av.getAlignment().getHeight(), res, alwidth = av
1416                 .getAlignment().getWidth(), g, gSize, f, fSize, sy;
1417         StringBuffer text = new StringBuffer();
1418         PrintWriter out = new PrintWriter(new FileWriter(imgMapFile));
1419         out.println(jalview.io.HTMLOutput.getImageMapHTML());
1420         out.println("<img src=\"" + imageName
1421                 + "\" border=\"0\" usemap=\"#Map\" >"
1422                 + "<map name=\"Map\">");
1423
1424         for (s = 0; s < sSize; s++)
1425         {
1426           sy = s * av.getCharHeight() + scaleHeight;
1427
1428           SequenceI seq = av.getAlignment().getSequenceAt(s);
1429           SequenceFeature[] features = seq.getSequenceFeatures();
1430           SequenceGroup[] groups = av.getAlignment().findAllGroups(seq);
1431           for (res = 0; res < alwidth; res++)
1432           {
1433             text = new StringBuffer();
1434             String triplet = null;
1435             if (av.getAlignment().isNucleotide())
1436             {
1437               triplet = ResidueProperties.nucleotideName.get(seq
1438                       .getCharAt(res) + "");
1439             }
1440             else
1441             {
1442               triplet = ResidueProperties.aa2Triplet.get(seq.getCharAt(res)
1443                       + "");
1444             }
1445
1446             if (triplet == null)
1447             {
1448               continue;
1449             }
1450
1451             int alIndex = seq.findPosition(res);
1452             gSize = groups.length;
1453             for (g = 0; g < gSize; g++)
1454             {
1455               if (text.length() < 1)
1456               {
1457                 text.append("<area shape=\"rect\" coords=\""
1458                         + (idWidth + res * av.getCharWidth()) + "," + sy
1459                         + "," + (idWidth + (res + 1) * av.getCharWidth())
1460                         + "," + (av.getCharHeight() + sy) + "\""
1461                         + " onMouseOver=\"toolTip('" + alIndex + " "
1462                         + triplet);
1463               }
1464
1465               if (groups[g].getStartRes() < res
1466                       && groups[g].getEndRes() > res)
1467               {
1468                 text.append("<br><em>" + groups[g].getName() + "</em>");
1469               }
1470             }
1471
1472             if (features != null)
1473             {
1474               if (text.length() < 1)
1475               {
1476                 text.append("<area shape=\"rect\" coords=\""
1477                         + (idWidth + res * av.getCharWidth()) + "," + sy
1478                         + "," + (idWidth + (res + 1) * av.getCharWidth())
1479                         + "," + (av.getCharHeight() + sy) + "\""
1480                         + " onMouseOver=\"toolTip('" + alIndex + " "
1481                         + triplet);
1482               }
1483               fSize = features.length;
1484               for (f = 0; f < fSize; f++)
1485               {
1486
1487                 if ((features[f].getBegin() <= seq.findPosition(res))
1488                         && (features[f].getEnd() >= seq.findPosition(res)))
1489                 {
1490                   if (features[f].getType().equals("disulfide bond"))
1491                   {
1492                     if (features[f].getBegin() == seq.findPosition(res)
1493                             || features[f].getEnd() == seq
1494                                     .findPosition(res))
1495                     {
1496                       text.append("<br>disulfide bond "
1497                               + features[f].getBegin() + ":"
1498                               + features[f].getEnd());
1499                     }
1500                   }
1501                   else
1502                   {
1503                     text.append("<br>");
1504                     text.append(features[f].getType());
1505                     if (features[f].getDescription() != null
1506                             && !features[f].getType().equals(
1507                                     features[f].getDescription()))
1508                     {
1509                       text.append(" " + features[f].getDescription());
1510                     }
1511
1512                     if (features[f].getValue("status") != null)
1513                     {
1514                       text.append(" (" + features[f].getValue("status")
1515                               + ")");
1516                     }
1517                   }
1518                 }
1519
1520               }
1521             }
1522             if (text.length() > 1)
1523             {
1524               text.append("')\"; onMouseOut=\"toolTip()\";  href=\"#\">");
1525               out.println(text.toString());
1526             }
1527           }
1528         }
1529         out.println("</map></body></html>");
1530         out.close();
1531
1532       } catch (Exception ex)
1533       {
1534         ex.printStackTrace();
1535       }
1536     } // /////////END OF IMAGE MAP
1537
1538   }
1539
1540   int getWrappedHeight()
1541   {
1542     int seqPanelWidth = getSeqPanel().seqCanvas.getWidth();
1543
1544     if (System.getProperty("java.awt.headless") != null
1545             && System.getProperty("java.awt.headless").equals("true"))
1546     {
1547       seqPanelWidth = alignFrame.getWidth() - getVisibleIdWidth()
1548               - vscroll.getPreferredSize().width
1549               - alignFrame.getInsets().left - alignFrame.getInsets().right;
1550     }
1551
1552     int chunkWidth = getSeqPanel().seqCanvas
1553             .getWrappedCanvasWidth(seqPanelWidth);
1554
1555     int hgap = av.getCharHeight();
1556     if (av.getScaleAboveWrapped())
1557     {
1558       hgap += av.getCharHeight();
1559     }
1560
1561     int annotationHeight = 0;
1562     if (av.isShowAnnotation())
1563     {
1564       annotationHeight = getAnnotationPanel().adjustPanelHeight();
1565     }
1566
1567     int cHeight = av.getAlignment().getHeight() * av.getCharHeight() + hgap
1568             + annotationHeight;
1569
1570     int maxwidth = av.getAlignment().getWidth();
1571     if (av.hasHiddenColumns())
1572     {
1573       maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
1574     }
1575
1576     int height = ((maxwidth / chunkWidth) + 1) * cHeight;
1577
1578     return height;
1579   }
1580
1581   /**
1582    * close the panel - deregisters all listeners and nulls any references to
1583    * alignment data.
1584    */
1585   public void closePanel()
1586   {
1587     PaintRefresher.RemoveComponent(getSeqPanel().seqCanvas);
1588     PaintRefresher.RemoveComponent(getIdPanel().getIdCanvas());
1589     PaintRefresher.RemoveComponent(this);
1590     if (av != null)
1591     {
1592       jalview.structure.StructureSelectionManager ssm = av
1593               .getStructureSelectionManager();
1594       ssm.removeStructureViewerListener(getSeqPanel(), null);
1595       ssm.removeSelectionListener(getSeqPanel());
1596       ssm.removeCommandListener(av);
1597       ssm.removeStructureViewerListener(getSeqPanel(), null);
1598       ssm.removeSelectionListener(getSeqPanel());
1599       av.setAlignment(null);
1600       av = null;
1601     }
1602     else
1603     {
1604       if (Cache.log.isDebugEnabled())
1605       {
1606         Cache.log.warn("Closing alignment panel which is already closed.");
1607       }
1608     }
1609   }
1610
1611   /**
1612    * hides or shows dynamic annotation rows based on groups and av state flags
1613    */
1614   public void updateAnnotation()
1615   {
1616     updateAnnotation(false, false);
1617   }
1618
1619   public void updateAnnotation(boolean applyGlobalSettings)
1620   {
1621     updateAnnotation(applyGlobalSettings, false);
1622   }
1623
1624   public void updateAnnotation(boolean applyGlobalSettings,
1625           boolean preserveNewGroupSettings)
1626   {
1627     av.updateGroupAnnotationSettings(applyGlobalSettings,
1628             preserveNewGroupSettings);
1629     adjustAnnotationHeight();
1630   }
1631
1632   @Override
1633   public AlignmentI getAlignment()
1634   {
1635     return av == null ? null : av.getAlignment();
1636   }
1637
1638   @Override
1639   public String getViewName()
1640   {
1641     return av.viewName;
1642   }
1643
1644   /**
1645    * Make/Unmake this alignment panel the current input focus
1646    * 
1647    * @param b
1648    */
1649   public void setSelected(boolean b)
1650   {
1651     try
1652     {
1653       if (alignFrame.getSplitViewContainer() != null)
1654       {
1655         /*
1656          * bring enclosing SplitFrame to front first if there is one
1657          */
1658         ((SplitFrame) alignFrame.getSplitViewContainer()).setSelected(b);
1659       }
1660       alignFrame.setSelected(b);
1661     } catch (Exception ex)
1662     {
1663     }
1664
1665     if (b)
1666     {
1667       alignFrame.setDisplayedView(this);
1668     }
1669   }
1670
1671   @Override
1672   public StructureSelectionManager getStructureSelectionManager()
1673   {
1674     return av.getStructureSelectionManager();
1675   }
1676
1677   @Override
1678   public void raiseOOMWarning(String string, OutOfMemoryError error)
1679   {
1680     new OOMWarning(string, error, this);
1681   }
1682
1683   @Override
1684   public jalview.api.FeatureRenderer cloneFeatureRenderer()
1685   {
1686
1687     return new FeatureRenderer(this);
1688   }
1689
1690   @Override
1691   public jalview.api.FeatureRenderer getFeatureRenderer()
1692   {
1693     return seqPanel.seqCanvas.getFeatureRenderer();
1694   }
1695
1696   public void updateFeatureRenderer(
1697           jalview.renderer.seqfeatures.FeatureRenderer fr)
1698   {
1699     fr.transferSettings(getSeqPanel().seqCanvas.getFeatureRenderer());
1700   }
1701
1702   public void updateFeatureRendererFrom(jalview.api.FeatureRenderer fr)
1703   {
1704     if (getSeqPanel().seqCanvas.getFeatureRenderer() != null)
1705     {
1706       getSeqPanel().seqCanvas.getFeatureRenderer().transferSettings(fr);
1707     }
1708   }
1709
1710   public ScalePanel getScalePanel()
1711   {
1712     return scalePanel;
1713   }
1714
1715   public void setScalePanel(ScalePanel scalePanel)
1716   {
1717     this.scalePanel = scalePanel;
1718   }
1719
1720   public SeqPanel getSeqPanel()
1721   {
1722     return seqPanel;
1723   }
1724
1725   public void setSeqPanel(SeqPanel seqPanel)
1726   {
1727     this.seqPanel = seqPanel;
1728   }
1729
1730   public AnnotationPanel getAnnotationPanel()
1731   {
1732     return annotationPanel;
1733   }
1734
1735   public void setAnnotationPanel(AnnotationPanel annotationPanel)
1736   {
1737     this.annotationPanel = annotationPanel;
1738   }
1739
1740   public AnnotationLabels getAlabels()
1741   {
1742     return alabels;
1743   }
1744
1745   public void setAlabels(AnnotationLabels alabels)
1746   {
1747     this.alabels = alabels;
1748   }
1749
1750   public IdPanel getIdPanel()
1751   {
1752     return idPanel;
1753   }
1754
1755   public void setIdPanel(IdPanel idPanel)
1756   {
1757     this.idPanel = idPanel;
1758   }
1759
1760   /**
1761    * Follow a scrolling change in the (cDNA/Protein) complementary alignment.
1762    * The aim is to keep the two alignments 'lined up' on their centre columns.
1763    * 
1764    * @param sr
1765    *          holds mapped region(s) of this alignment that we are scrolling
1766    *          'to'; may be modified for sequence offset by this method
1767    * @param verticalOffset
1768    *          the number of visible sequences to show above the mapped region
1769    */
1770   public void scrollToCentre(SearchResults sr, int verticalOffset)
1771   {
1772     /*
1773      * To avoid jumpy vertical scrolling (if some sequences are gapped or not
1774      * mapped), we can make the scroll-to location a sequence above the one
1775      * actually mapped.
1776      */
1777     SequenceI mappedTo = sr.getResultSequence(0);
1778     List<SequenceI> seqs = av.getAlignment().getSequences();
1779
1780     /*
1781      * This is like AlignmentI.findIndex(seq) but here we are matching the
1782      * dataset sequence not the aligned sequence
1783      */
1784     boolean matched = false;
1785     for (SequenceI seq : seqs)
1786     {
1787       if (mappedTo == seq.getDatasetSequence())
1788       {
1789         matched = true;
1790         break;
1791       }
1792     }
1793     if (!matched)
1794     {
1795       return; // failsafe, shouldn't happen
1796     }
1797
1798     /*
1799      * Scroll to position but centring the target residue.
1800      */
1801     scrollToPosition(sr, verticalOffset, true, true);
1802   }
1803
1804   /**
1805    * Set a flag to say do not scroll any (cDNA/protein) complement.
1806    * 
1807    * @param b
1808    */
1809   protected void setDontScrollComplement(boolean b)
1810   {
1811     this.dontScrollComplement = b;
1812   }
1813
1814   protected boolean isDontScrollComplement()
1815   {
1816     return this.dontScrollComplement;
1817   }
1818 }