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