Merge branch 'develop' into Release_2_9_0b1_Branch
[jalview.git] / src / jalview / gui / AlignmentPanel.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.9)
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 followingComplementScroll;
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     hscroll.setValues(x, hextent, 0, width);
736     vscroll.setValues(y, vextent, 0, height);
737   }
738
739   /**
740    * DOCUMENT ME!
741    * 
742    * @param evt
743    *          DOCUMENT ME!
744    */
745   public void adjustmentValueChanged(AdjustmentEvent evt)
746   {
747     int oldX = av.getStartRes();
748     int oldY = av.getStartSeq();
749
750     if (evt.getSource() == hscroll)
751     {
752       int x = hscroll.getValue();
753       av.setStartRes(x);
754       av.setEndRes((x + (getSeqPanel().seqCanvas.getWidth() / av
755               .getCharWidth())) - 1);
756     }
757
758     if (evt.getSource() == vscroll)
759     {
760       int offy = vscroll.getValue();
761
762       if (av.getWrapAlignment())
763       {
764         if (offy > -1)
765         {
766           int rowSize = getSeqPanel().seqCanvas
767                   .getWrappedCanvasWidth(getSeqPanel().seqCanvas.getWidth());
768           av.setStartRes(offy * rowSize);
769           av.setEndRes((offy + 1) * rowSize);
770         }
771         else
772         {
773           // This is only called if file loaded is a jar file that
774           // was wrapped when saved and user has wrap alignment true
775           // as preference setting
776           SwingUtilities.invokeLater(new Runnable()
777           {
778             public void run()
779             {
780               setScrollValues(av.getStartRes(), av.getStartSeq());
781             }
782           });
783         }
784       }
785       else
786       {
787         av.setStartSeq(offy);
788         av.setEndSeq(offy
789                 + (getSeqPanel().seqCanvas.getHeight() / av.getCharHeight()));
790       }
791     }
792
793     if (overviewPanel != null)
794     {
795       overviewPanel.setBoxPosition();
796     }
797
798     int scrollX = av.startRes - oldX;
799     int scrollY = av.startSeq - oldY;
800
801     if (av.getWrapAlignment() || !fastPaint)
802     {
803       repaint();
804     }
805     else
806     {
807       // Make sure we're not trying to draw a panel
808       // larger than the visible window
809       if (scrollX > av.endRes - av.startRes)
810       {
811         scrollX = av.endRes - av.startRes;
812       }
813       else if (scrollX < av.startRes - av.endRes)
814       {
815         scrollX = av.startRes - av.endRes;
816       }
817
818       if (scrollX != 0 || scrollY != 0)
819       {
820         getIdPanel().getIdCanvas().fastPaint(scrollY);
821         getSeqPanel().seqCanvas.fastPaint(scrollX, scrollY);
822         getScalePanel().repaint();
823
824         if (av.isShowAnnotation() && scrollX != 0)
825         {
826           getAnnotationPanel().fastPaint(scrollX);
827         }
828       }
829     }
830     /*
831      * If there is one, scroll the (Protein/cDNA) complementary alignment to
832      * match, unless we are ourselves doing that.
833      */
834     if (isFollowingComplementScroll())
835     {
836       setFollowingComplementScroll(false);
837     }
838     else
839     {
840       av.scrollComplementaryAlignment();
841     }
842   }
843
844   /**
845    * Repaint the alignment including the annotations and overview panels (if
846    * shown).
847    */
848   public void paintAlignment(boolean updateOverview)
849   {
850     final AnnotationSorter sorter = new AnnotationSorter(getAlignment(),
851             av.isShowAutocalculatedAbove());
852     sorter.sort(getAlignment().getAlignmentAnnotation(),
853             av.getSortAnnotationsBy());
854     repaint();
855
856     if (updateOverview)
857     {
858       // TODO: determine if this paintAlignment changed structure colours
859       av.getStructureSelectionManager().sequenceColoursChanged(this);
860
861       if (overviewPanel != null)
862       {
863         overviewPanel.updateOverviewImage();
864       }
865     }
866   }
867
868   /**
869    * DOCUMENT ME!
870    * 
871    * @param g
872    *          DOCUMENT ME!
873    */
874   public void paintComponent(Graphics g)
875   {
876     invalidate();
877
878     Dimension d = getIdPanel().getIdCanvas().getPreferredSize();
879     idPanelHolder.setPreferredSize(d);
880     hscrollFillerPanel.setPreferredSize(new Dimension(d.width, 12));
881     validate();
882
883     if (av.getWrapAlignment())
884     {
885       int maxwidth = av.getAlignment().getWidth();
886
887       if (av.hasHiddenColumns())
888       {
889         maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
890       }
891
892       int canvasWidth = getSeqPanel().seqCanvas
893               .getWrappedCanvasWidth(getSeqPanel().seqCanvas.getWidth());
894       if (canvasWidth > 0)
895       {
896         int max = maxwidth
897                 / getSeqPanel().seqCanvas
898                         .getWrappedCanvasWidth(getSeqPanel().seqCanvas
899                                 .getWidth()) + 1;
900         vscroll.setMaximum(max);
901         vscroll.setUnitIncrement(1);
902         vscroll.setVisibleAmount(1);
903       }
904     }
905     else
906     {
907       setScrollValues(av.getStartRes(), av.getStartSeq());
908     }
909   }
910
911   /**
912    * DOCUMENT ME!
913    * 
914    * @param pg
915    *          DOCUMENT ME!
916    * @param pf
917    *          DOCUMENT ME!
918    * @param pi
919    *          DOCUMENT ME!
920    * 
921    * @return DOCUMENT ME!
922    * 
923    * @throws PrinterException
924    *           DOCUMENT ME!
925    */
926   public int print(Graphics pg, PageFormat pf, int pi)
927           throws PrinterException
928   {
929     pg.translate((int) pf.getImageableX(), (int) pf.getImageableY());
930
931     int pwidth = (int) pf.getImageableWidth();
932     int pheight = (int) pf.getImageableHeight();
933
934     if (av.getWrapAlignment())
935     {
936       return printWrappedAlignment(pg, pwidth, pheight, pi);
937     }
938     else
939     {
940       return printUnwrapped(pg, pwidth, pheight, pi);
941     }
942   }
943
944   /**
945    * DOCUMENT ME!
946    * 
947    * @param pg
948    *          DOCUMENT ME!
949    * @param pwidth
950    *          DOCUMENT ME!
951    * @param pheight
952    *          DOCUMENT ME!
953    * @param pi
954    *          DOCUMENT ME!
955    * 
956    * @return DOCUMENT ME!
957    * 
958    * @throws PrinterException
959    *           DOCUMENT ME!
960    */
961   public int printUnwrapped(Graphics pg, int pwidth, int pheight, int pi)
962           throws PrinterException
963   {
964     int idWidth = getVisibleIdWidth(false);
965     FontMetrics fm = getFontMetrics(av.getFont());
966     int scaleHeight = av.getCharHeight() + fm.getDescent();
967
968     pg.setColor(Color.white);
969     pg.fillRect(0, 0, pwidth, pheight);
970     pg.setFont(av.getFont());
971
972     // //////////////////////////////////
973     // / How many sequences and residues can we fit on a printable page?
974     int totalRes = (pwidth - idWidth) / av.getCharWidth();
975
976     int totalSeq = (pheight - scaleHeight) / av.getCharHeight() - 1;
977
978     int pagesWide = (av.getAlignment().getWidth() / totalRes) + 1;
979
980     // ///////////////////////////
981     // / Only print these sequences and residues on this page
982     int startRes;
983
984     // ///////////////////////////
985     // / Only print these sequences and residues on this page
986     int endRes;
987
988     // ///////////////////////////
989     // / Only print these sequences and residues on this page
990     int startSeq;
991
992     // ///////////////////////////
993     // / Only print these sequences and residues on this page
994     int endSeq;
995     startRes = (pi % pagesWide) * totalRes;
996     endRes = (startRes + totalRes) - 1;
997
998     if (endRes > (av.getAlignment().getWidth() - 1))
999     {
1000       endRes = av.getAlignment().getWidth() - 1;
1001     }
1002
1003     startSeq = (pi / pagesWide) * totalSeq;
1004     endSeq = startSeq + totalSeq;
1005
1006     if (endSeq > av.getAlignment().getHeight())
1007     {
1008       endSeq = av.getAlignment().getHeight();
1009     }
1010
1011     int pagesHigh = ((av.getAlignment().getHeight() / totalSeq) + 1)
1012             * pheight;
1013
1014     if (av.isShowAnnotation())
1015     {
1016       pagesHigh += getAnnotationPanel().adjustPanelHeight() + 3;
1017     }
1018
1019     pagesHigh /= pheight;
1020
1021     if (pi >= (pagesWide * pagesHigh))
1022     {
1023       return Printable.NO_SUCH_PAGE;
1024     }
1025
1026     // draw Scale
1027     pg.translate(idWidth, 0);
1028     getScalePanel().drawScale(pg, startRes, endRes, pwidth - idWidth,
1029             scaleHeight);
1030     pg.translate(-idWidth, scaleHeight);
1031
1032     // //////////////
1033     // Draw the ids
1034     Color currentColor = null;
1035     Color currentTextColor = null;
1036
1037     pg.setFont(getIdPanel().getIdCanvas().getIdfont());
1038
1039     SequenceI seq;
1040     for (int i = startSeq; i < endSeq; i++)
1041     {
1042       seq = av.getAlignment().getSequenceAt(i);
1043       if ((av.getSelectionGroup() != null)
1044               && av.getSelectionGroup().getSequences(null).contains(seq))
1045       {
1046         currentColor = Color.gray;
1047         currentTextColor = Color.black;
1048       }
1049       else
1050       {
1051         currentColor = av.getSequenceColour(seq);
1052         currentTextColor = Color.black;
1053       }
1054
1055       pg.setColor(currentColor);
1056       pg.fillRect(0, (i - startSeq) * av.getCharHeight(), idWidth,
1057               av.getCharHeight());
1058
1059       pg.setColor(currentTextColor);
1060
1061       int xPos = 0;
1062       if (av.isRightAlignIds())
1063       {
1064         fm = pg.getFontMetrics();
1065         xPos = idWidth
1066                 - fm.stringWidth(seq.getDisplayId(av.getShowJVSuffix()))
1067                 - 4;
1068       }
1069
1070       pg.drawString(seq.getDisplayId(av.getShowJVSuffix()), xPos,
1071               (((i - startSeq) * av.getCharHeight()) + av.getCharHeight())
1072                       - (av.getCharHeight() / 5));
1073     }
1074
1075     pg.setFont(av.getFont());
1076
1077     // draw main sequence panel
1078     pg.translate(idWidth, 0);
1079     getSeqPanel().seqCanvas.drawPanel(pg, startRes, endRes, startSeq,
1080             endSeq, 0);
1081
1082     if (av.isShowAnnotation() && (endSeq == av.getAlignment().getHeight()))
1083     {
1084       // draw annotation - need to offset for current scroll position
1085       int offset = -getAlabels().getScrollOffset();
1086       pg.translate(0, offset);
1087       pg.translate(-idWidth - 3, (endSeq - startSeq) * av.getCharHeight()
1088               + 3);
1089       getAlabels().drawComponent(pg, idWidth);
1090       pg.translate(idWidth + 3, 0);
1091       getAnnotationPanel().renderer.drawComponent(getAnnotationPanel(), av,
1092               pg, -1, startRes, endRes + 1);
1093       pg.translate(0, -offset);
1094     }
1095
1096     return Printable.PAGE_EXISTS;
1097   }
1098
1099   /**
1100    * DOCUMENT ME!
1101    * 
1102    * @param pg
1103    *          DOCUMENT ME!
1104    * @param pwidth
1105    *          DOCUMENT ME!
1106    * @param pheight
1107    *          DOCUMENT ME!
1108    * @param pi
1109    *          DOCUMENT ME!
1110    * 
1111    * @return DOCUMENT ME!
1112    * 
1113    * @throws PrinterException
1114    *           DOCUMENT ME!
1115    */
1116   public int printWrappedAlignment(Graphics pg, int pwidth, int pheight,
1117           int pi) throws PrinterException
1118   {
1119     int annotationHeight = 0;
1120     AnnotationLabels labels = null;
1121     if (av.isShowAnnotation())
1122     {
1123       annotationHeight = getAnnotationPanel().adjustPanelHeight();
1124       labels = new AnnotationLabels(av);
1125     }
1126
1127     int hgap = av.getCharHeight();
1128     if (av.getScaleAboveWrapped())
1129     {
1130       hgap += av.getCharHeight();
1131     }
1132
1133     int cHeight = av.getAlignment().getHeight() * av.getCharHeight() + hgap
1134             + annotationHeight;
1135
1136     int idWidth = getVisibleIdWidth(false);
1137
1138     int maxwidth = av.getAlignment().getWidth();
1139     if (av.hasHiddenColumns())
1140     {
1141       maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
1142     }
1143
1144     int resWidth = getSeqPanel().seqCanvas.getWrappedCanvasWidth(pwidth
1145             - idWidth);
1146
1147     int totalHeight = cHeight * (maxwidth / resWidth + 1);
1148
1149     pg.setColor(Color.white);
1150     pg.fillRect(0, 0, pwidth, pheight);
1151     pg.setFont(av.getFont());
1152
1153     // //////////////
1154     // Draw the ids
1155     pg.setColor(Color.black);
1156
1157     pg.translate(0, -pi * pheight);
1158
1159     pg.setClip(0, pi * pheight, pwidth, pheight);
1160
1161     int ypos = hgap;
1162
1163     do
1164     {
1165       for (int i = 0; i < av.getAlignment().getHeight(); i++)
1166       {
1167         pg.setFont(getIdPanel().getIdCanvas().getIdfont());
1168         SequenceI s = av.getAlignment().getSequenceAt(i);
1169         String string = s.getDisplayId(av.getShowJVSuffix());
1170         int xPos = 0;
1171         if (av.isRightAlignIds())
1172         {
1173           FontMetrics fm = pg.getFontMetrics();
1174           xPos = idWidth - fm.stringWidth(string) - 4;
1175         }
1176         pg.drawString(string, xPos,
1177                 ((i * av.getCharHeight()) + ypos + av.getCharHeight())
1178                         - (av.getCharHeight() / 5));
1179       }
1180       if (labels != null)
1181       {
1182         pg.translate(-3,
1183                 ypos + (av.getAlignment().getHeight() * av.getCharHeight()));
1184
1185         pg.setFont(av.getFont());
1186         labels.drawComponent(pg, idWidth);
1187         pg.translate(
1188                 +3,
1189                 -ypos
1190                         - (av.getAlignment().getHeight() * av
1191                                 .getCharHeight()));
1192       }
1193
1194       ypos += cHeight;
1195     } while (ypos < totalHeight);
1196
1197     pg.translate(idWidth, 0);
1198
1199     getSeqPanel().seqCanvas.drawWrappedPanel(pg, pwidth - idWidth,
1200             totalHeight, 0);
1201
1202     if ((pi * pheight) < totalHeight)
1203     {
1204       return Printable.PAGE_EXISTS;
1205
1206     }
1207     else
1208     {
1209       return Printable.NO_SUCH_PAGE;
1210     }
1211   }
1212
1213   /**
1214    * get current sequence ID panel width, or nominal value if panel were to be
1215    * displayed using default settings
1216    * 
1217    * @return
1218    */
1219   public int getVisibleIdWidth()
1220   {
1221     return getVisibleIdWidth(true);
1222   }
1223
1224   /**
1225    * get current sequence ID panel width, or nominal value if panel were to be
1226    * displayed using default settings
1227    * 
1228    * @param onscreen
1229    *          indicate if the Id width for onscreen or offscreen display should
1230    *          be returned
1231    * @return
1232    */
1233   public int getVisibleIdWidth(boolean onscreen)
1234   {
1235     // see if rendering offscreen - check preferences and calc width accordingly
1236     if (!onscreen && Cache.getDefault("FIGURE_AUTOIDWIDTH", false))
1237     {
1238       return calculateIdWidth(-1).width + 4;
1239     }
1240     Integer idwidth = null;
1241     if (onscreen
1242             || (idwidth = Cache.getIntegerProperty("FIGURE_FIXEDIDWIDTH")) == null)
1243     {
1244       return (getIdPanel().getWidth() > 0 ? getIdPanel().getWidth()
1245               : calculateIdWidth().width + 4);
1246     }
1247     return idwidth.intValue() + 4;
1248   }
1249
1250   void makeAlignmentImage(jalview.util.ImageMaker.TYPE type, File file)
1251   {
1252     long progress = System.currentTimeMillis();
1253     headless = (System.getProperty("java.awt.headless") != null && System
1254             .getProperty("java.awt.headless").equals("true"));
1255     if (alignFrame != null && !headless)
1256     {
1257       alignFrame.setProgressBar(MessageManager.formatMessage(
1258               "status.saving_file", new Object[] { type.getLabel() }),
1259               progress);
1260     }
1261     try
1262     {
1263       AlignmentDimension aDimension = getAlignmentDimension();
1264       try
1265       {
1266         jalview.util.ImageMaker im;
1267         final String imageAction, imageTitle;
1268         if (type == jalview.util.ImageMaker.TYPE.PNG)
1269         {
1270           imageAction = "Create PNG image from alignment";
1271           imageTitle = null;
1272         }
1273         else if (type == jalview.util.ImageMaker.TYPE.EPS)
1274         {
1275           imageAction = "Create EPS file from alignment";
1276           imageTitle = alignFrame.getTitle();
1277         }
1278         else
1279         {
1280           imageAction = "Create SVG file from alignment";
1281           imageTitle = alignFrame.getTitle();
1282         }
1283
1284         im = new jalview.util.ImageMaker(this, type, imageAction,
1285                 aDimension.getWidth(), aDimension.getHeight(), file,
1286                 imageTitle);
1287         if (av.getWrapAlignment())
1288         {
1289           if (im.getGraphics() != null)
1290           {
1291             printWrappedAlignment(im.getGraphics(), aDimension.getWidth(),
1292                     aDimension.getHeight(), 0);
1293             im.writeImage();
1294           }
1295         }
1296         else
1297         {
1298           if (im.getGraphics() != null)
1299           {
1300             printUnwrapped(im.getGraphics(), aDimension.getWidth(),
1301                     aDimension.getHeight(), 0);
1302             im.writeImage();
1303           }
1304         }
1305       } catch (OutOfMemoryError err)
1306       {
1307         // Be noisy here.
1308         System.out.println("########################\n" + "OUT OF MEMORY "
1309                 + file + "\n" + "########################");
1310         new OOMWarning("Creating Image for " + file, err);
1311         // System.out.println("Create IMAGE: " + err);
1312       } catch (Exception ex)
1313       {
1314         ex.printStackTrace();
1315       }
1316     } finally
1317     {
1318       if (alignFrame != null && !headless)
1319       {
1320         alignFrame.setProgressBar(
1321                 MessageManager.getString("status.export_complete"),
1322                 progress);
1323       }
1324     }
1325   }
1326
1327   public AlignmentDimension getAlignmentDimension()
1328   {
1329     int maxwidth = av.getAlignment().getWidth();
1330     if (av.hasHiddenColumns())
1331     {
1332       maxwidth = av.getColumnSelection().findColumnPosition(maxwidth);
1333     }
1334
1335     int height = ((av.getAlignment().getHeight() + 1) * av.getCharHeight())
1336             + getScalePanel().getHeight();
1337     int width = getVisibleIdWidth(false) + (maxwidth * av.getCharWidth());
1338
1339     if (av.getWrapAlignment())
1340     {
1341       height = getWrappedHeight();
1342       if (headless)
1343       {
1344         // need to obtain default alignment width and then add in any
1345         // additional allowance for id margin
1346         // this duplicates the calculation in getWrappedHeight but adjusts for
1347         // offscreen idWith
1348         width = alignFrame.getWidth() - vscroll.getPreferredSize().width
1349                 - alignFrame.getInsets().left
1350                 - alignFrame.getInsets().right - getVisibleIdWidth()
1351                 + getVisibleIdWidth(false);
1352       }
1353       else
1354       {
1355         width = getSeqPanel().getWidth() + getVisibleIdWidth(false);
1356       }
1357
1358     }
1359     else if (av.isShowAnnotation())
1360     {
1361       height += getAnnotationPanel().adjustPanelHeight() + 3;
1362     }
1363     return new AlignmentDimension(width, height);
1364
1365   }
1366
1367   /**
1368    * DOCUMENT ME!
1369    */
1370   public void makeEPS(File epsFile)
1371   {
1372     makeAlignmentImage(jalview.util.ImageMaker.TYPE.EPS, epsFile);
1373   }
1374
1375   /**
1376    * DOCUMENT ME!
1377    */
1378   public void makePNG(File pngFile)
1379   {
1380     makeAlignmentImage(jalview.util.ImageMaker.TYPE.PNG, pngFile);
1381   }
1382
1383   public void makeSVG(File svgFile)
1384   {
1385     makeAlignmentImage(jalview.util.ImageMaker.TYPE.SVG, svgFile);
1386   }
1387
1388   public void makePNGImageMap(File imgMapFile, String imageName)
1389   {
1390     // /////ONLY WORKS WITH NONE WRAPPED ALIGNMENTS
1391     // ////////////////////////////////////////////
1392     int idWidth = getVisibleIdWidth(false);
1393     FontMetrics fm = getFontMetrics(av.getFont());
1394     int scaleHeight = av.getCharHeight() + fm.getDescent();
1395
1396     // Gen image map
1397     // ////////////////////////////////
1398     if (imgMapFile != null)
1399     {
1400       try
1401       {
1402         int s, sSize = av.getAlignment().getHeight(), res, alwidth = av
1403                 .getAlignment().getWidth(), g, gSize, f, fSize, sy;
1404         StringBuffer text = new StringBuffer();
1405         PrintWriter out = new PrintWriter(new FileWriter(imgMapFile));
1406         out.println(jalview.io.HTMLOutput.getImageMapHTML());
1407         out.println("<img src=\"" + imageName
1408                 + "\" border=\"0\" usemap=\"#Map\" >"
1409                 + "<map name=\"Map\">");
1410
1411         for (s = 0; s < sSize; s++)
1412         {
1413           sy = s * av.getCharHeight() + scaleHeight;
1414
1415           SequenceI seq = av.getAlignment().getSequenceAt(s);
1416           SequenceFeature[] features = seq.getSequenceFeatures();
1417           SequenceGroup[] groups = av.getAlignment().findAllGroups(seq);
1418           for (res = 0; res < alwidth; res++)
1419           {
1420             text = new StringBuffer();
1421             String triplet = null;
1422             if (av.getAlignment().isNucleotide())
1423             {
1424               triplet = ResidueProperties.nucleotideName.get(seq
1425                       .getCharAt(res) + "");
1426             }
1427             else
1428             {
1429               triplet = ResidueProperties.aa2Triplet.get(seq.getCharAt(res)
1430                       + "");
1431             }
1432
1433             if (triplet == null)
1434             {
1435               continue;
1436             }
1437
1438             int alIndex = seq.findPosition(res);
1439             gSize = groups.length;
1440             for (g = 0; g < gSize; g++)
1441             {
1442               if (text.length() < 1)
1443               {
1444                 text.append("<area shape=\"rect\" coords=\""
1445                         + (idWidth + res * av.getCharWidth()) + "," + sy
1446                         + "," + (idWidth + (res + 1) * av.getCharWidth())
1447                         + "," + (av.getCharHeight() + sy) + "\""
1448                         + " onMouseOver=\"toolTip('" + alIndex + " "
1449                         + triplet);
1450               }
1451
1452               if (groups[g].getStartRes() < res
1453                       && groups[g].getEndRes() > res)
1454               {
1455                 text.append("<br><em>" + groups[g].getName() + "</em>");
1456               }
1457             }
1458
1459             if (features != null)
1460             {
1461               if (text.length() < 1)
1462               {
1463                 text.append("<area shape=\"rect\" coords=\""
1464                         + (idWidth + res * av.getCharWidth()) + "," + sy
1465                         + "," + (idWidth + (res + 1) * av.getCharWidth())
1466                         + "," + (av.getCharHeight() + sy) + "\""
1467                         + " onMouseOver=\"toolTip('" + alIndex + " "
1468                         + triplet);
1469               }
1470               fSize = features.length;
1471               for (f = 0; f < fSize; f++)
1472               {
1473
1474                 if ((features[f].getBegin() <= seq.findPosition(res))
1475                         && (features[f].getEnd() >= seq.findPosition(res)))
1476                 {
1477                   if (features[f].getType().equals("disulfide bond"))
1478                   {
1479                     if (features[f].getBegin() == seq.findPosition(res)
1480                             || features[f].getEnd() == seq
1481                                     .findPosition(res))
1482                     {
1483                       text.append("<br>disulfide bond "
1484                               + features[f].getBegin() + ":"
1485                               + features[f].getEnd());
1486                     }
1487                   }
1488                   else
1489                   {
1490                     text.append("<br>");
1491                     text.append(features[f].getType());
1492                     if (features[f].getDescription() != null
1493                             && !features[f].getType().equals(
1494                                     features[f].getDescription()))
1495                     {
1496                       text.append(" " + features[f].getDescription());
1497                     }
1498
1499                     if (features[f].getValue("status") != null)
1500                     {
1501                       text.append(" (" + features[f].getValue("status")
1502                               + ")");
1503                     }
1504                   }
1505                 }
1506
1507               }
1508             }
1509             if (text.length() > 1)
1510             {
1511               text.append("')\"; onMouseOut=\"toolTip()\";  href=\"#\">");
1512               out.println(text.toString());
1513             }
1514           }
1515         }
1516         out.println("</map></body></html>");
1517         out.close();
1518
1519       } catch (Exception ex)
1520       {
1521         ex.printStackTrace();
1522       }
1523     } // /////////END OF IMAGE MAP
1524
1525   }
1526
1527   int getWrappedHeight()
1528   {
1529     int seqPanelWidth = getSeqPanel().seqCanvas.getWidth();
1530
1531     if (System.getProperty("java.awt.headless") != null
1532             && System.getProperty("java.awt.headless").equals("true"))
1533     {
1534       seqPanelWidth = alignFrame.getWidth() - getVisibleIdWidth()
1535               - vscroll.getPreferredSize().width
1536               - alignFrame.getInsets().left - alignFrame.getInsets().right;
1537     }
1538
1539     int chunkWidth = getSeqPanel().seqCanvas
1540             .getWrappedCanvasWidth(seqPanelWidth);
1541
1542     int hgap = av.getCharHeight();
1543     if (av.getScaleAboveWrapped())
1544     {
1545       hgap += av.getCharHeight();
1546     }
1547
1548     int annotationHeight = 0;
1549     if (av.isShowAnnotation())
1550     {
1551       annotationHeight = getAnnotationPanel().adjustPanelHeight();
1552     }
1553
1554     int cHeight = av.getAlignment().getHeight() * av.getCharHeight() + hgap
1555             + annotationHeight;
1556
1557     int maxwidth = av.getAlignment().getWidth();
1558     if (av.hasHiddenColumns())
1559     {
1560       maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
1561     }
1562
1563     int height = ((maxwidth / chunkWidth) + 1) * cHeight;
1564
1565     return height;
1566   }
1567
1568   /**
1569    * close the panel - deregisters all listeners and nulls any references to
1570    * alignment data.
1571    */
1572   public void closePanel()
1573   {
1574     PaintRefresher.RemoveComponent(getSeqPanel().seqCanvas);
1575     PaintRefresher.RemoveComponent(getIdPanel().getIdCanvas());
1576     PaintRefresher.RemoveComponent(this);
1577     if (av != null)
1578     {
1579       jalview.structure.StructureSelectionManager ssm = av
1580               .getStructureSelectionManager();
1581       ssm.removeStructureViewerListener(getSeqPanel(), null);
1582       ssm.removeSelectionListener(getSeqPanel());
1583       ssm.removeCommandListener(av);
1584       ssm.removeStructureViewerListener(getSeqPanel(), null);
1585       ssm.removeSelectionListener(getSeqPanel());
1586       av.setAlignment(null);
1587       av = null;
1588     }
1589     else
1590     {
1591       if (Cache.log.isDebugEnabled())
1592       {
1593         Cache.log.warn("Closing alignment panel which is already closed.");
1594       }
1595     }
1596   }
1597
1598   /**
1599    * hides or shows dynamic annotation rows based on groups and av state flags
1600    */
1601   public void updateAnnotation()
1602   {
1603     updateAnnotation(false, false);
1604   }
1605
1606   public void updateAnnotation(boolean applyGlobalSettings)
1607   {
1608     updateAnnotation(applyGlobalSettings, false);
1609   }
1610
1611   public void updateAnnotation(boolean applyGlobalSettings,
1612           boolean preserveNewGroupSettings)
1613   {
1614     av.updateGroupAnnotationSettings(applyGlobalSettings,
1615             preserveNewGroupSettings);
1616     adjustAnnotationHeight();
1617   }
1618
1619   @Override
1620   public AlignmentI getAlignment()
1621   {
1622     return av == null ? null : av.getAlignment();
1623   }
1624
1625   @Override
1626   public String getViewName()
1627   {
1628     return av.viewName;
1629   }
1630
1631   /**
1632    * Make/Unmake this alignment panel the current input focus
1633    * 
1634    * @param b
1635    */
1636   public void setSelected(boolean b)
1637   {
1638     try
1639     {
1640       if (alignFrame.getSplitViewContainer() != null)
1641       {
1642         /*
1643          * bring enclosing SplitFrame to front first if there is one
1644          */
1645         ((SplitFrame) alignFrame.getSplitViewContainer()).setSelected(b);
1646       }
1647       alignFrame.setSelected(b);
1648     } catch (Exception ex)
1649     {
1650     }
1651
1652     if (b)
1653     {
1654       alignFrame.setDisplayedView(this);
1655     }
1656   }
1657
1658   @Override
1659   public StructureSelectionManager getStructureSelectionManager()
1660   {
1661     return av.getStructureSelectionManager();
1662   }
1663
1664   @Override
1665   public void raiseOOMWarning(String string, OutOfMemoryError error)
1666   {
1667     new OOMWarning(string, error, this);
1668   }
1669
1670   @Override
1671   public jalview.api.FeatureRenderer cloneFeatureRenderer()
1672   {
1673
1674     return new FeatureRenderer(this);
1675   }
1676
1677   @Override
1678   public jalview.api.FeatureRenderer getFeatureRenderer()
1679   {
1680     return seqPanel.seqCanvas.getFeatureRenderer();
1681   }
1682
1683   public void updateFeatureRenderer(
1684           jalview.renderer.seqfeatures.FeatureRenderer fr)
1685   {
1686     fr.transferSettings(getSeqPanel().seqCanvas.getFeatureRenderer());
1687   }
1688
1689   public void updateFeatureRendererFrom(jalview.api.FeatureRenderer fr)
1690   {
1691     if (getSeqPanel().seqCanvas.getFeatureRenderer() != null)
1692     {
1693       getSeqPanel().seqCanvas.getFeatureRenderer().transferSettings(fr);
1694     }
1695   }
1696
1697   public ScalePanel getScalePanel()
1698   {
1699     return scalePanel;
1700   }
1701
1702   public void setScalePanel(ScalePanel scalePanel)
1703   {
1704     this.scalePanel = scalePanel;
1705   }
1706
1707   public SeqPanel getSeqPanel()
1708   {
1709     return seqPanel;
1710   }
1711
1712   public void setSeqPanel(SeqPanel seqPanel)
1713   {
1714     this.seqPanel = seqPanel;
1715   }
1716
1717   public AnnotationPanel getAnnotationPanel()
1718   {
1719     return annotationPanel;
1720   }
1721
1722   public void setAnnotationPanel(AnnotationPanel annotationPanel)
1723   {
1724     this.annotationPanel = annotationPanel;
1725   }
1726
1727   public AnnotationLabels getAlabels()
1728   {
1729     return alabels;
1730   }
1731
1732   public void setAlabels(AnnotationLabels alabels)
1733   {
1734     this.alabels = alabels;
1735   }
1736
1737   public IdPanel getIdPanel()
1738   {
1739     return idPanel;
1740   }
1741
1742   public void setIdPanel(IdPanel idPanel)
1743   {
1744     this.idPanel = idPanel;
1745   }
1746
1747   /**
1748    * Follow a scrolling change in the (cDNA/Protein) complementary alignment.
1749    * The aim is to keep the two alignments 'lined up' on their centre columns.
1750    * 
1751    * @param sr
1752    *          holds mapped region(s) of this alignment that we are scrolling
1753    *          'to'; may be modified for sequence offset by this method
1754    * @param verticalOffset
1755    *          the number of visible sequences to show above the mapped region
1756    */
1757   public void scrollToCentre(SearchResults sr, int verticalOffset)
1758   {
1759     /*
1760      * To avoid jumpy vertical scrolling (if some sequences are gapped or not
1761      * mapped), we can make the scroll-to location a sequence above the one
1762      * actually mapped.
1763      */
1764     SequenceI mappedTo = sr.getResultSequence(0);
1765     List<SequenceI> seqs = av.getAlignment().getSequences();
1766
1767     /*
1768      * This is like AlignmentI.findIndex(seq) but here we are matching the
1769      * dataset sequence not the aligned sequence
1770      */
1771     boolean matched = false;
1772     for (SequenceI seq : seqs)
1773     {
1774       if (mappedTo == seq.getDatasetSequence())
1775       {
1776         matched = true;
1777         break;
1778       }
1779     }
1780     if (!matched)
1781     {
1782       return; // failsafe, shouldn't happen
1783     }
1784
1785     /*
1786      * Scroll to position but centring the target residue.
1787      */
1788     scrollToPosition(sr, verticalOffset, true, true);
1789   }
1790
1791   /**
1792    * Set a flag to say we are scrolling to follow a (cDNA/protein) complement.
1793    * 
1794    * @param b
1795    */
1796   protected void setFollowingComplementScroll(boolean b)
1797   {
1798     this.followingComplementScroll = b;
1799   }
1800
1801   protected boolean isFollowingComplementScroll()
1802   {
1803     return this.followingComplementScroll;
1804   }
1805 }