(JAL-985,JAL-986) allow different ID margin calculation for on and off screen rendering
[jalview.git] / src / jalview / gui / AlignmentPanel.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.7)
3  * Copyright (C) 2011 J Procter, AM Waterhouse, G Barton, M Clamp, S Searle
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 of the License, or (at your option) any later version.
10  * 
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 package jalview.gui;
19
20 import java.beans.*;
21 import java.io.*;
22 import java.util.Hashtable;
23 import java.util.Vector;
24
25 import java.awt.*;
26 import java.awt.event.*;
27 import java.awt.print.*;
28 import javax.swing.*;
29
30 import jalview.api.AlignmentViewPanel;
31 import jalview.bin.Cache;
32 import jalview.datamodel.*;
33 import jalview.jbgui.*;
34 import jalview.schemes.*;
35 import jalview.structure.SelectionSource;
36 import jalview.structure.StructureSelectionManager;
37
38 /**
39  * DOCUMENT ME!
40  * 
41  * @author $author$
42  * @version $Revision: 1.161 $
43  */
44 public class AlignmentPanel extends GAlignmentPanel implements
45         AdjustmentListener, Printable, AlignmentViewPanel
46 {
47   public AlignViewport av;
48
49   OverviewPanel overviewPanel;
50
51   SeqPanel seqPanel;
52
53   IdPanel idPanel;
54
55   IdwidthAdjuster idwidthAdjuster;
56
57   /** DOCUMENT ME!! */
58   public AlignFrame alignFrame;
59
60   ScalePanel scalePanel;
61
62   AnnotationPanel annotationPanel;
63
64   AnnotationLabels alabels;
65
66   // this value is set false when selection area being dragged
67   boolean fastPaint = true;
68
69   int hextent = 0;
70
71   int vextent = 0;
72
73   /**
74    * Creates a new AlignmentPanel object.
75    * 
76    * @param af
77    *          DOCUMENT ME!
78    * @param av
79    *          DOCUMENT ME!
80    */
81   public AlignmentPanel(AlignFrame af, final AlignViewport av)
82   {
83     alignFrame = af;
84     this.av = av;
85     seqPanel = new SeqPanel(av, this);
86     idPanel = new IdPanel(av, this);
87
88     scalePanel = new ScalePanel(av, this);
89
90     idPanelHolder.add(idPanel, BorderLayout.CENTER);
91     idwidthAdjuster = new IdwidthAdjuster(this);
92     idSpaceFillerPanel1.add(idwidthAdjuster, BorderLayout.CENTER);
93
94     annotationPanel = new AnnotationPanel(this);
95     alabels = new AnnotationLabels(this);
96
97     annotationScroller.setViewportView(annotationPanel);
98     annotationSpaceFillerHolder.add(alabels, BorderLayout.CENTER);
99
100     scalePanelHolder.add(scalePanel, BorderLayout.CENTER);
101     seqPanelHolder.add(seqPanel, BorderLayout.CENTER);
102
103     setScrollValues(0, 0);
104
105     setAnnotationVisible(av.getShowAnnotation());
106
107     hscroll.addAdjustmentListener(this);
108     vscroll.addAdjustmentListener(this);
109
110     final AlignmentPanel ap = this;
111     av.addPropertyChangeListener(new PropertyChangeListener()
112     {
113       public void propertyChange(PropertyChangeEvent evt)
114       {
115         if (evt.getPropertyName().equals("alignment"))
116         {
117           PaintRefresher.Refresh(ap, av.getSequenceSetId(), true, true);
118           alignmentChanged();
119         }
120       }
121     });
122     fontChanged();
123     adjustAnnotationHeight();
124
125   }
126
127   public void alignmentChanged()
128   {
129     av.alignmentChanged(this);
130
131     alignFrame.updateEditMenuBar();
132
133     paintAlignment(true);
134
135   }
136
137   /**
138    * DOCUMENT ME!
139    */
140   public void fontChanged()
141   {
142     // set idCanvas bufferedImage to null
143     // to prevent drawing old image
144     FontMetrics fm = getFontMetrics(av.getFont());
145
146     scalePanelHolder.setPreferredSize(new Dimension(10, av.charHeight
147             + fm.getDescent()));
148     idSpaceFillerPanel1.setPreferredSize(new Dimension(10, av.charHeight
149             + fm.getDescent()));
150
151     idPanel.idCanvas.gg = null;
152     seqPanel.seqCanvas.img = null;
153     annotationPanel.adjustPanelHeight();
154
155     Dimension d = calculateIdWidth();
156     d.setSize(d.width + 4, d.height);
157     idPanel.idCanvas.setPreferredSize(d);
158     hscrollFillerPanel.setPreferredSize(d);
159
160     if (overviewPanel != null)
161     {
162       overviewPanel.setBoxPosition();
163     }
164
165     repaint();
166   }
167
168   /**
169    * Calculate the width of the alignment labels based on the displayed names
170    * and any bounds on label width set in preferences.
171    * 
172    * @return Dimension giving the maximum width of the alignment label panel
173    *         that should be used.
174    */
175   public Dimension calculateIdWidth()
176   {
177     // calculate sensible default width when no preference is available
178     
179     int afwidth = (alignFrame != null ? alignFrame.getWidth() : 300);
180     int maxwidth = Math.max(20,
181             Math.min(afwidth - 200, (int) 2 * afwidth / 3));
182     return calculateIdWidth(maxwidth);
183   }
184   /**
185    * Calculate the width of the alignment labels based on the displayed names
186    * and any bounds on label width set in preferences.
187    * @param maxwidth -1 or maximum width allowed for IdWidth
188    * @return Dimension giving the maximum width of the alignment label panel
189    *         that should be used.
190    */
191   public Dimension calculateIdWidth(int maxwidth)
192   {
193     Container c = new Container();
194
195     FontMetrics fm = c.getFontMetrics(new Font(av.font.getName(),
196             Font.ITALIC, av.font.getSize()));
197
198     AlignmentI al = av.getAlignment();
199     int i = 0;
200     int idWidth = 0;
201     String id;
202
203     while ((i < al.getHeight()) && (al.getSequenceAt(i) != null))
204     {
205       SequenceI s = al.getSequenceAt(i);
206
207       id = s.getDisplayId(av.getShowJVSuffix());
208
209       if (fm.stringWidth(id) > idWidth)
210       {
211         idWidth = fm.stringWidth(id);
212       }
213
214       i++;
215     }
216
217     // Also check annotation label widths
218     i = 0;
219
220     if (al.getAlignmentAnnotation() != null)
221     {
222       fm = c.getFontMetrics(alabels.getFont());
223
224       while (i < al.getAlignmentAnnotation().length)
225       {
226         String label = al.getAlignmentAnnotation()[i].label;
227
228         if (fm.stringWidth(label) > idWidth)
229         {
230           idWidth = fm.stringWidth(label);
231         }
232
233         i++;
234       }
235     }
236
237     return new Dimension(maxwidth<0 ? idWidth : Math.min(maxwidth, idWidth), 12);
238   }
239
240   /**
241    * Highlight the given results on the alignment.
242    * 
243    */
244   public void highlightSearchResults(SearchResults results)
245   {
246     scrollToPosition(results);
247     seqPanel.seqCanvas.highlightSearchResults(results);
248   }
249
250   /**
251    * scroll the view to show the position of the highlighted region in results
252    * (if any) and redraw the overview
253    * 
254    * @param results
255    */
256   public boolean scrollToPosition(SearchResults results)
257   {
258     return scrollToPosition(results, true);
259   }
260
261   /**
262    * scroll the view to show the position of the highlighted region in results
263    * (if any)
264    * 
265    * @param results
266    * @param redrawOverview
267    *          - when set, the overview will be recalculated (takes longer)
268    * @return false if results were not found
269    */
270   public boolean scrollToPosition(SearchResults results,
271           boolean redrawOverview)
272   {
273     int startv, endv, starts, ends, width;
274     // TODO: properly locate search results in view when large numbers of hidden
275     // columns exist before highlighted region
276     // do we need to scroll the panel?
277     // TODO: tons of nullpointereexceptions raised here.
278     if (results != null && results.getSize() > 0 && av != null
279             && av.alignment != null)
280     {
281       int seqIndex = av.alignment.findIndex(results);
282       if (seqIndex == -1)
283       {
284         return false;
285       }
286       SequenceI seq = av.alignment.getSequenceAt(seqIndex);
287       
288       int[] r=results.getResults(seq, 0, av.alignment.getWidth());
289       if (r == null)
290       {
291         return false;
292       }
293       int start = r[0];
294       int end = r[1];
295       // System.err.println("Seq : "+seqIndex+" Scroll to "+start+","+end); //
296       // DEBUG
297       if (start < 0)
298       {
299         return false;
300       }
301       if (end == seq.getEnd())
302       {
303         return false;
304       }
305       if (av.hasHiddenColumns)
306       {
307         start = av.getColumnSelection().findColumnPosition(start);
308         end = av.getColumnSelection().findColumnPosition(end);
309         if (start==end)
310         {
311           if (!av.colSel.isVisible(r[0]))
312           {
313             // don't scroll - position isn't visible
314             return false;
315           }
316         }
317       }
318       if (!av.wrapAlignment)
319       {
320         if ((startv = av.getStartRes()) >= start)
321         {
322           setScrollValues(start - 1, seqIndex);
323         }
324         else if ((endv = av.getEndRes()) <= end)
325         {
326           setScrollValues(startv + 1 + end - endv, seqIndex);
327         }
328         else if ((starts = av.getStartSeq()) > seqIndex)
329         {
330           setScrollValues(av.getStartRes(), seqIndex);
331         }
332         else if ((ends = av.getEndSeq()) <= seqIndex)
333         {
334           setScrollValues(av.getStartRes(), starts + seqIndex - ends + 1);
335         }
336       }
337       else
338       {
339         scrollToWrappedVisible(start);
340       }
341     }
342     if (redrawOverview && overviewPanel != null)
343     {
344       overviewPanel.setBoxPosition();
345     }
346     paintAlignment(redrawOverview);
347     return true;
348   }
349
350   void scrollToWrappedVisible(int res)
351   {
352     int cwidth = seqPanel.seqCanvas
353             .getWrappedCanvasWidth(seqPanel.seqCanvas.getWidth());
354     if (res < av.getStartRes() || res >= (av.getStartRes() + cwidth))
355     {
356       vscroll.setValue((res / cwidth));
357       av.startRes = vscroll.getValue() * cwidth;
358     }
359
360   }
361
362   /**
363    * DOCUMENT ME!
364    * 
365    * @return DOCUMENT ME!
366    */
367   public OverviewPanel getOverviewPanel()
368   {
369     return overviewPanel;
370   }
371
372   /**
373    * DOCUMENT ME!
374    * 
375    * @param op
376    *          DOCUMENT ME!
377    */
378   public void setOverviewPanel(OverviewPanel op)
379   {
380     overviewPanel = op;
381   }
382
383   /**
384    * 
385    * @param b Hide or show annotation panel
386    *          
387    */
388   public void setAnnotationVisible(boolean b)
389   {
390     if (!av.wrapAlignment)
391     {
392       annotationSpaceFillerHolder.setVisible(b);
393       annotationScroller.setVisible(b);
394     }
395     repaint();
396   }
397
398   /**
399    * automatically adjust annotation panel height for new annotation
400    * whilst ensuring the alignment is still visible.
401    */
402   public void adjustAnnotationHeight()
403   {
404     // TODO: display vertical annotation scrollbar if necessary
405     // this is called after loading new annotation onto alignment
406     if (alignFrame.getHeight() == 0)
407     {
408       System.out.println("NEEDS FIXING");
409     }
410     validateAnnotationDimensions(true);
411     addNotify();
412     paintAlignment(true);
413   }
414   /**
415    * calculate the annotation dimensions and refresh slider values accordingly.
416    * need to do repaints/notifys afterwards. 
417    */
418   protected void validateAnnotationDimensions(boolean adjustPanelHeight) {
419     int height = annotationPanel.adjustPanelHeight();
420     
421     if (hscroll.isVisible())
422     {
423       height += hscroll.getPreferredSize().height;
424     }
425     if (height > alignFrame.getHeight() / 2)
426     {
427       height = alignFrame.getHeight() / 2;
428     }
429     if (!adjustPanelHeight)
430     {
431       // maintain same window layout whilst updating sliders
432       height=annotationScroller.getSize().height;
433     }
434     hscroll.addNotify();
435     
436     annotationScroller.setPreferredSize(new Dimension(annotationScroller
437             .getWidth(), height));
438
439     annotationSpaceFillerHolder.setPreferredSize(new Dimension(
440             annotationSpaceFillerHolder.getWidth(), height));
441     annotationScroller.validate();// repaint();
442     annotationScroller.addNotify();
443   }
444
445   /**
446    * DOCUMENT ME!
447    * 
448    * @param wrap
449    *          DOCUMENT ME!
450    */
451   public void setWrapAlignment(boolean wrap)
452   {
453     av.startSeq = 0;
454     scalePanelHolder.setVisible(!wrap);
455     hscroll.setVisible(!wrap);
456     idwidthAdjuster.setVisible(!wrap);
457
458     if (wrap)
459     {
460       annotationScroller.setVisible(false);
461       annotationSpaceFillerHolder.setVisible(false);
462     }
463     else if (av.showAnnotation)
464     {
465       annotationScroller.setVisible(true);
466       annotationSpaceFillerHolder.setVisible(true);
467     }
468
469     idSpaceFillerPanel1.setVisible(!wrap);
470
471     repaint();
472   }
473
474   // return value is true if the scroll is valid
475   public boolean scrollUp(boolean up)
476   {
477     if (up)
478     {
479       if (vscroll.getValue() < 1)
480       {
481         return false;
482       }
483
484       fastPaint = false;
485       vscroll.setValue(vscroll.getValue() - 1);
486     }
487     else
488     {
489       if ((vextent + vscroll.getValue()) >= av.getAlignment().getHeight())
490       {
491         return false;
492       }
493
494       fastPaint = false;
495       vscroll.setValue(vscroll.getValue() + 1);
496     }
497
498     fastPaint = true;
499
500     return true;
501   }
502
503   /**
504    * DOCUMENT ME!
505    * 
506    * @param right
507    *          DOCUMENT ME!
508    * 
509    * @return DOCUMENT ME!
510    */
511   public boolean scrollRight(boolean right)
512   {
513     if (!right)
514     {
515       if (hscroll.getValue() < 1)
516       {
517         return false;
518       }
519
520       fastPaint = false;
521       hscroll.setValue(hscroll.getValue() - 1);
522     }
523     else
524     {
525       if ((hextent + hscroll.getValue()) >= av.getAlignment().getWidth())
526       {
527         return false;
528       }
529
530       fastPaint = false;
531       hscroll.setValue(hscroll.getValue() + 1);
532     }
533
534     fastPaint = true;
535
536     return true;
537   }
538
539   /**
540    * Adjust row/column scrollers to show a visible position in the alignment.
541    * 
542    * @param x visible column to scroll to
543    *          DOCUMENT ME!
544    * @param y visible row to scroll to
545    *          
546    */
547   public void setScrollValues(int x, int y)
548   {
549     // System.err.println("Scroll to "+x+","+y);
550     if (av == null || av.alignment == null)
551     {
552       return;
553     }
554     int width = av.alignment.getWidth();
555     int height = av.alignment.getHeight();
556
557     if (av.hasHiddenColumns)
558     {
559       width = av.getColumnSelection().findColumnPosition(width);
560     }
561
562     av.setEndRes((x + (seqPanel.seqCanvas.getWidth() / av.charWidth)) - 1);
563
564     hextent = seqPanel.seqCanvas.getWidth() / av.charWidth;
565     vextent = seqPanel.seqCanvas.getHeight() / av.charHeight;
566
567     if (hextent > width)
568     {
569       hextent = width;
570     }
571
572     if (vextent > height)
573     {
574       vextent = height;
575     }
576
577     if ((hextent + x) > width)
578     {
579       x = width - hextent;
580     }
581
582     if ((vextent + y) > height)
583     {
584       y = height - vextent;
585     }
586
587     if (y < 0)
588     {
589       y = 0;
590     }
591
592     if (x < 0)
593     {
594       x = 0;
595     }
596
597     hscroll.setValues(x, hextent, 0, width);
598     vscroll.setValues(y, vextent, 0, height);
599   }
600
601   /**
602    * DOCUMENT ME!
603    * 
604    * @param evt
605    *          DOCUMENT ME!
606    */
607   public void adjustmentValueChanged(AdjustmentEvent evt)
608   {
609
610     int oldX = av.getStartRes();
611     int oldY = av.getStartSeq();
612
613     if (evt.getSource() == hscroll)
614     {
615       int x = hscroll.getValue();
616       av.setStartRes(x);
617       av.setEndRes((x + (seqPanel.seqCanvas.getWidth() / av.getCharWidth())) - 1);
618     }
619
620     if (evt.getSource() == vscroll)
621     {
622       int offy = vscroll.getValue();
623
624       if (av.getWrapAlignment())
625       {
626         if (offy > -1)
627         {
628           int rowSize = seqPanel.seqCanvas
629                   .getWrappedCanvasWidth(seqPanel.seqCanvas.getWidth());
630           av.setStartRes(offy * rowSize);
631           av.setEndRes((offy + 1) * rowSize);
632         }
633         else
634         {
635           // This is only called if file loaded is a jar file that
636           // was wrapped when saved and user has wrap alignment true
637           // as preference setting
638           SwingUtilities.invokeLater(new Runnable()
639           {
640             public void run()
641             {
642               setScrollValues(av.getStartRes(), av.getStartSeq());
643             }
644           });
645         }
646       }
647       else
648       {
649         av.setStartSeq(offy);
650         av.setEndSeq(offy
651                 + (seqPanel.seqCanvas.getHeight() / av.getCharHeight()));
652       }
653     }
654
655     if (overviewPanel != null)
656     {
657       overviewPanel.setBoxPosition();
658     }
659
660     int scrollX = av.startRes - oldX;
661     int scrollY = av.startSeq - oldY;
662
663     if (av.getWrapAlignment() || !fastPaint)
664     {
665       repaint();
666     }
667     else
668     {
669       // Make sure we're not trying to draw a panel
670       // larger than the visible window
671       if (scrollX > av.endRes - av.startRes)
672       {
673         scrollX = av.endRes - av.startRes;
674       }
675       else if (scrollX < av.startRes - av.endRes)
676       {
677         scrollX = av.startRes - av.endRes;
678       }
679
680       if (scrollX != 0 || scrollY != 0)
681       {
682         idPanel.idCanvas.fastPaint(scrollY);
683         seqPanel.seqCanvas.fastPaint(scrollX, scrollY);
684         scalePanel.repaint();
685
686         if (av.getShowAnnotation())
687         {
688           annotationPanel.fastPaint(scrollX);
689         }
690       }
691     }
692   }
693
694   public void paintAlignment(boolean updateOverview)
695   {
696     repaint();
697
698     if (updateOverview)
699     {
700       av.getStructureSelectionManager().sequenceColoursChanged(this);
701
702       if (overviewPanel != null)
703       {
704         overviewPanel.updateOverviewImage();
705       }
706     }
707   }
708
709   /**
710    * DOCUMENT ME!
711    * 
712    * @param g
713    *          DOCUMENT ME!
714    */
715   public void paintComponent(Graphics g)
716   {
717     invalidate();
718
719     Dimension d = idPanel.idCanvas.getPreferredSize();
720     idPanelHolder.setPreferredSize(d);
721     hscrollFillerPanel.setPreferredSize(new Dimension(d.width, 12));
722     validate();
723
724     if (av.getWrapAlignment())
725     {
726       int maxwidth = av.alignment.getWidth();
727
728       if (av.hasHiddenColumns)
729       {
730         maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
731       }
732
733       int canvasWidth = seqPanel.seqCanvas
734               .getWrappedCanvasWidth(seqPanel.seqCanvas.getWidth());
735       if (canvasWidth > 0)
736       {
737         int max = maxwidth
738                 / seqPanel.seqCanvas
739                         .getWrappedCanvasWidth(seqPanel.seqCanvas
740                                 .getWidth()) + 1;
741         vscroll.setMaximum(max);
742         vscroll.setUnitIncrement(1);
743         vscroll.setVisibleAmount(1);
744       }
745     }
746     else
747     {
748       setScrollValues(av.getStartRes(), av.getStartSeq());
749     }
750   }
751
752   /**
753    * DOCUMENT ME!
754    * 
755    * @param pg
756    *          DOCUMENT ME!
757    * @param pf
758    *          DOCUMENT ME!
759    * @param pi
760    *          DOCUMENT ME!
761    * 
762    * @return DOCUMENT ME!
763    * 
764    * @throws PrinterException
765    *           DOCUMENT ME!
766    */
767   public int print(Graphics pg, PageFormat pf, int pi)
768           throws PrinterException
769   {
770     pg.translate((int) pf.getImageableX(), (int) pf.getImageableY());
771
772     int pwidth = (int) pf.getImageableWidth();
773     int pheight = (int) pf.getImageableHeight();
774
775     if (av.getWrapAlignment())
776     {
777       return printWrappedAlignment(pg, pwidth, pheight, pi);
778     }
779     else
780     {
781       return printUnwrapped(pg, pwidth, pheight, pi);
782     }
783   }
784
785   /**
786    * DOCUMENT ME!
787    * 
788    * @param pg
789    *          DOCUMENT ME!
790    * @param pwidth
791    *          DOCUMENT ME!
792    * @param pheight
793    *          DOCUMENT ME!
794    * @param pi
795    *          DOCUMENT ME!
796    * 
797    * @return DOCUMENT ME!
798    * 
799    * @throws PrinterException
800    *           DOCUMENT ME!
801    */
802   public int printUnwrapped(Graphics pg, int pwidth, int pheight, int pi)
803           throws PrinterException
804   {
805     int idWidth = getVisibleIdWidth(false);
806     FontMetrics fm = getFontMetrics(av.getFont());
807     int scaleHeight = av.charHeight + fm.getDescent();
808
809     pg.setColor(Color.white);
810     pg.fillRect(0, 0, pwidth, pheight);
811     pg.setFont(av.getFont());
812
813     // //////////////////////////////////
814     // / How many sequences and residues can we fit on a printable page?
815     int totalRes = (pwidth - idWidth) / av.getCharWidth();
816
817     int totalSeq = (int) ((pheight - scaleHeight) / av.getCharHeight()) - 1;
818
819     int pagesWide = (av.getAlignment().getWidth() / totalRes) + 1;
820
821     // ///////////////////////////
822     // / Only print these sequences and residues on this page
823     int startRes;
824
825     // ///////////////////////////
826     // / Only print these sequences and residues on this page
827     int endRes;
828
829     // ///////////////////////////
830     // / Only print these sequences and residues on this page
831     int startSeq;
832
833     // ///////////////////////////
834     // / Only print these sequences and residues on this page
835     int endSeq;
836     startRes = (pi % pagesWide) * totalRes;
837     endRes = (startRes + totalRes) - 1;
838
839     if (endRes > (av.getAlignment().getWidth() - 1))
840     {
841       endRes = av.getAlignment().getWidth() - 1;
842     }
843
844     startSeq = (pi / pagesWide) * totalSeq;
845     endSeq = startSeq + totalSeq;
846
847     if (endSeq > av.getAlignment().getHeight())
848     {
849       endSeq = av.getAlignment().getHeight();
850     }
851
852     int pagesHigh = ((av.alignment.getHeight() / totalSeq) + 1) * pheight;
853
854     if (av.showAnnotation)
855     {
856       pagesHigh += annotationPanel.adjustPanelHeight() + 3;
857     }
858
859     pagesHigh /= pheight;
860
861     if (pi >= (pagesWide * pagesHigh))
862     {
863       return Printable.NO_SUCH_PAGE;
864     }
865
866     // draw Scale
867     pg.translate(idWidth, 0);
868     scalePanel.drawScale(pg, startRes, endRes, pwidth - idWidth,
869             scaleHeight);
870     pg.translate(-idWidth, scaleHeight);
871
872     // //////////////
873     // Draw the ids
874     Color currentColor = null;
875     Color currentTextColor = null;
876
877     pg.setFont(idPanel.idCanvas.idfont);
878
879     SequenceI seq;
880     for (int i = startSeq; i < endSeq; i++)
881     {
882       seq = av.getAlignment().getSequenceAt(i);
883       if ((av.getSelectionGroup() != null)
884               && av.getSelectionGroup().getSequences(null).contains(seq))
885       {
886         currentColor = Color.gray;
887         currentTextColor = Color.black;
888       }
889       else
890       {
891         currentColor = av.getSequenceColour(seq);
892         currentTextColor = Color.black;
893       }
894
895       pg.setColor(currentColor);
896       pg.fillRect(0, (i - startSeq) * av.charHeight, idWidth,
897               av.getCharHeight());
898
899       pg.setColor(currentTextColor);
900
901       int xPos = 0;
902       if (av.rightAlignIds)
903       {
904         fm = pg.getFontMetrics();
905         xPos = idWidth
906                 - fm.stringWidth(seq.getDisplayId(av.getShowJVSuffix()))
907                 - 4;
908       }
909
910       pg.drawString(
911               seq.getDisplayId(av.getShowJVSuffix()),
912               xPos,
913               (((i - startSeq) * av.charHeight) + av.getCharHeight())
914                       - (av.getCharHeight() / 5));
915     }
916
917     pg.setFont(av.getFont());
918
919     // draw main sequence panel
920     pg.translate(idWidth, 0);
921     seqPanel.seqCanvas.drawPanel(pg, startRes, endRes, startSeq, endSeq, 0);
922
923     if (av.showAnnotation && (endSeq == av.alignment.getHeight()))
924     {
925       pg.translate(-idWidth - 3, (endSeq - startSeq) * av.charHeight + 3);
926       alabels.drawComponent((Graphics2D) pg, idWidth);
927       pg.translate(idWidth + 3, 0);
928       annotationPanel.drawComponent((Graphics2D) pg, startRes, endRes + 1);
929     }
930
931     return Printable.PAGE_EXISTS;
932   }
933
934   /**
935    * DOCUMENT ME!
936    * 
937    * @param pg
938    *          DOCUMENT ME!
939    * @param pwidth
940    *          DOCUMENT ME!
941    * @param pheight
942    *          DOCUMENT ME!
943    * @param pi
944    *          DOCUMENT ME!
945    * 
946    * @return DOCUMENT ME!
947    * 
948    * @throws PrinterException
949    *           DOCUMENT ME!
950    */
951   public int printWrappedAlignment(Graphics pg, int pwidth, int pheight,
952           int pi) throws PrinterException
953   {
954
955     int annotationHeight = 0;
956     AnnotationLabels labels = null;
957     if (av.showAnnotation)
958     {
959       annotationHeight = annotationPanel.adjustPanelHeight();
960       labels = new AnnotationLabels(av);
961     }
962
963     int hgap = av.charHeight;
964     if (av.scaleAboveWrapped)
965     {
966       hgap += av.charHeight;
967     }
968
969     int cHeight = av.getAlignment().getHeight() * av.charHeight + hgap
970             + annotationHeight;
971
972     int idWidth = getVisibleIdWidth(false);
973
974     int maxwidth = av.alignment.getWidth();
975     if (av.hasHiddenColumns)
976     {
977       maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
978     }
979
980     int resWidth = seqPanel.seqCanvas.getWrappedCanvasWidth(pwidth
981             - idWidth);
982
983     int totalHeight = cHeight * (maxwidth / resWidth + 1);
984
985     pg.setColor(Color.white);
986     pg.fillRect(0, 0, pwidth, pheight);
987     pg.setFont(av.getFont());
988
989     // //////////////
990     // Draw the ids
991     pg.setColor(Color.black);
992
993     pg.translate(0, -pi * pheight);
994
995     pg.setClip(0, pi * pheight, pwidth, pheight);
996
997     int ypos = hgap;
998
999     do
1000     {
1001       for (int i = 0; i < av.alignment.getHeight(); i++)
1002       {
1003         pg.setFont(idPanel.idCanvas.idfont);
1004         SequenceI s = av.alignment.getSequenceAt(i);
1005         String string = s.getDisplayId(av.getShowJVSuffix());
1006         int xPos = 0;
1007         if (av.rightAlignIds)
1008         {
1009           FontMetrics fm = pg.getFontMetrics();
1010           xPos = idWidth - fm.stringWidth(string) - 4;
1011         }
1012         pg.drawString(string, xPos,
1013                 ((i * av.charHeight) + ypos + av.charHeight)
1014                         - (av.charHeight / 5));
1015       }
1016       if (labels != null)
1017       {
1018         pg.translate(-3, ypos
1019                 + (av.getAlignment().getHeight() * av.charHeight));
1020
1021         pg.setFont(av.getFont());
1022         labels.drawComponent(pg, idWidth);
1023         pg.translate(+3, -ypos
1024                 - (av.getAlignment().getHeight() * av.charHeight));
1025       }
1026
1027       ypos += cHeight;
1028     } while (ypos < totalHeight);
1029
1030     pg.translate(idWidth, 0);
1031
1032     seqPanel.seqCanvas.drawWrappedPanel(pg, pwidth - idWidth, totalHeight,
1033             0);
1034
1035     if ((pi * pheight) < totalHeight)
1036     {
1037       return Printable.PAGE_EXISTS;
1038
1039     }
1040     else
1041     {
1042       return Printable.NO_SUCH_PAGE;
1043     }
1044   }
1045   /**
1046    * get current sequence ID panel width, or nominal value if panel were to be displayed using default settings
1047    * @return
1048    */
1049   int getVisibleIdWidth()
1050   {
1051     return getVisibleIdWidth(true);
1052   }
1053
1054   /**
1055    * get current sequence ID panel width, or nominal value if panel were to be displayed using default settings
1056    * @param onscreen indicate if the Id width for onscreen or offscreen display should be returned
1057    * @return
1058    */
1059   int getVisibleIdWidth(boolean onscreen)
1060   {
1061     // see if rendering offscreen - check preferences and calc width accordingly
1062     if (!onscreen && Cache.getDefault("FIGURE_AUTOIDWIDTH", false))
1063     {
1064       return calculateIdWidth(-1).width+4;
1065     }
1066     Integer idwidth=null;
1067     if (onscreen || (idwidth=Cache.getIntegerProperty("FIGURE_FIXEDIDWIDTH"))==null) {
1068       return (idPanel.getWidth() > 0 ? idPanel.getWidth()
1069             : calculateIdWidth().width + 4);
1070     }
1071     return idwidth.intValue()+4;
1072   }
1073
1074   void makeAlignmentImage(int type, File file)
1075   {
1076     int maxwidth = av.alignment.getWidth();
1077     if (av.hasHiddenColumns)
1078     {
1079       maxwidth = av.getColumnSelection().findColumnPosition(maxwidth);
1080     }
1081
1082     int height = ((av.alignment.getHeight() + 1) * av.charHeight)
1083             + scalePanel.getHeight();
1084     int width = getVisibleIdWidth(false) + (maxwidth * av.charWidth);
1085
1086     if (av.getWrapAlignment())
1087     {
1088       height = getWrappedHeight();
1089       if (System.getProperty("java.awt.headless") != null
1090               && System.getProperty("java.awt.headless").equals("true"))
1091       {
1092         // need to obtain default alignment width and then add in any additional allowance for id margin
1093         // this duplicates the calculation in getWrappedHeight but adjusts for offscreen idWith
1094         width = alignFrame.getWidth() - vscroll.getPreferredSize().width
1095                 - alignFrame.getInsets().left
1096                 - alignFrame.getInsets().right
1097                 - getVisibleIdWidth()+getVisibleIdWidth(false);
1098       }
1099       else
1100       {
1101         width = seqPanel.getWidth() + getVisibleIdWidth(false);
1102       }
1103
1104     }
1105     else if (av.getShowAnnotation())
1106     {
1107       height += annotationPanel.adjustPanelHeight() + 3;
1108     }
1109
1110     try
1111     {
1112
1113       jalview.util.ImageMaker im;
1114       if (type == jalview.util.ImageMaker.PNG)
1115       {
1116         im = new jalview.util.ImageMaker(this, jalview.util.ImageMaker.PNG,
1117                 "Create PNG image from alignment", width, height, file,
1118                 null);
1119       }
1120       else
1121       {
1122         im = new jalview.util.ImageMaker(this, jalview.util.ImageMaker.EPS,
1123                 "Create EPS file from alignment", width, height, file,
1124                 alignFrame.getTitle());
1125       }
1126
1127       if (av.getWrapAlignment())
1128       {
1129         if (im.getGraphics() != null)
1130         {
1131           printWrappedAlignment(im.getGraphics(), width, height, 0);
1132           im.writeImage();
1133         }
1134       }
1135       else
1136       {
1137         if (im.getGraphics() != null)
1138         {
1139           printUnwrapped(im.getGraphics(), width, height, 0);
1140           im.writeImage();
1141         }
1142       }
1143     } catch (OutOfMemoryError err)
1144     {
1145       // Be noisy here.
1146       System.out.println("########################\n" + "OUT OF MEMORY "
1147               + file + "\n" + "########################");
1148       new OOMWarning("Creating Image for " + file, err);
1149       // System.out.println("Create IMAGE: " + err);
1150     } catch (Exception ex)
1151     {
1152       ex.printStackTrace();
1153     }
1154   }
1155
1156   /**
1157    * DOCUMENT ME!
1158    */
1159   public void makeEPS(File epsFile)
1160   {
1161     makeAlignmentImage(jalview.util.ImageMaker.EPS, epsFile);
1162   }
1163
1164   /**
1165    * DOCUMENT ME!
1166    */
1167   public void makePNG(File pngFile)
1168   {
1169     makeAlignmentImage(jalview.util.ImageMaker.PNG, pngFile);
1170   }
1171
1172   public void makePNGImageMap(File imgMapFile, String imageName)
1173   {
1174     // /////ONLY WORKS WITH NONE WRAPPED ALIGNMENTS
1175     // ////////////////////////////////////////////
1176     int idWidth = getVisibleIdWidth(true);
1177     FontMetrics fm = getFontMetrics(av.getFont());
1178     int scaleHeight = av.charHeight + fm.getDescent();
1179
1180     // Gen image map
1181     // ////////////////////////////////
1182     if (imgMapFile != null)
1183     {
1184       try
1185       {
1186         int s, sSize = av.alignment.getHeight(), res, alwidth = av.alignment
1187                 .getWidth(), g, gSize, f, fSize, sy;
1188         StringBuffer text = new StringBuffer();
1189         PrintWriter out = new PrintWriter(new FileWriter(imgMapFile));
1190         out.println(jalview.io.HTMLOutput.getImageMapHTML());
1191         out.println("<img src=\"" + imageName
1192                 + "\" border=\"0\" usemap=\"#Map\" >"
1193                 + "<map name=\"Map\">");
1194
1195         for (s = 0; s < sSize; s++)
1196         {
1197           sy = s * av.charHeight + scaleHeight;
1198
1199           SequenceI seq = av.alignment.getSequenceAt(s);
1200           SequenceFeature[] features = seq.getDatasetSequence()
1201                   .getSequenceFeatures();
1202           SequenceGroup[] groups = av.alignment.findAllGroups(seq);
1203           for (res = 0; res < alwidth; res++)
1204           {
1205             text = new StringBuffer();
1206             Object obj = null;
1207             if (av.alignment.isNucleotide())
1208             {
1209               obj = ResidueProperties.nucleotideName.get(seq.getCharAt(res)
1210                       + "");
1211             }
1212             else
1213             {
1214               obj = ResidueProperties.aa2Triplet.get(seq.getCharAt(res)
1215                       + "");
1216             }
1217
1218             if (obj == null)
1219             {
1220               continue;
1221             }
1222
1223             String triplet = obj.toString();
1224             int alIndex = seq.findPosition(res);
1225             gSize = groups.length;
1226             for (g = 0; g < gSize; g++)
1227             {
1228               if (text.length() < 1)
1229               {
1230                 text.append("<area shape=\"rect\" coords=\""
1231                         + (idWidth + res * av.charWidth) + "," + sy + ","
1232                         + (idWidth + (res + 1) * av.charWidth) + ","
1233                         + (av.charHeight + sy) + "\""
1234                         + " onMouseOver=\"toolTip('" + alIndex + " "
1235                         + triplet);
1236               }
1237
1238               if (groups[g].getStartRes() < res
1239                       && groups[g].getEndRes() > res)
1240               {
1241                 text.append("<br><em>" + groups[g].getName() + "</em>");
1242               }
1243             }
1244
1245             if (features != null)
1246             {
1247               if (text.length() < 1)
1248               {
1249                 text.append("<area shape=\"rect\" coords=\""
1250                         + (idWidth + res * av.charWidth) + "," + sy + ","
1251                         + (idWidth + (res + 1) * av.charWidth) + ","
1252                         + (av.charHeight + sy) + "\""
1253                         + " onMouseOver=\"toolTip('" + alIndex + " "
1254                         + triplet);
1255               }
1256               fSize = features.length;
1257               for (f = 0; f < fSize; f++)
1258               {
1259
1260                 if ((features[f].getBegin() <= seq.findPosition(res))
1261                         && (features[f].getEnd() >= seq.findPosition(res)))
1262                 {
1263                   if (features[f].getType().equals("disulfide bond"))
1264                   {
1265                     if (features[f].getBegin() == seq.findPosition(res)
1266                             || features[f].getEnd() == seq
1267                                     .findPosition(res))
1268                     {
1269                       text.append("<br>disulfide bond "
1270                               + features[f].getBegin() + ":"
1271                               + features[f].getEnd());
1272                     }
1273                   }
1274                   else
1275                   {
1276                     text.append("<br>");
1277                     text.append(features[f].getType());
1278                     if (features[f].getDescription() != null
1279                             && !features[f].getType().equals(
1280                                     features[f].getDescription()))
1281                     {
1282                       text.append(" " + features[f].getDescription());
1283                     }
1284
1285                     if (features[f].getValue("status") != null)
1286                     {
1287                       text.append(" (" + features[f].getValue("status")
1288                               + ")");
1289                     }
1290                   }
1291                 }
1292
1293               }
1294             }
1295             if (text.length() > 1)
1296             {
1297               text.append("')\"; onMouseOut=\"toolTip()\";  href=\"#\">");
1298               out.println(text.toString());
1299             }
1300           }
1301         }
1302         out.println("</map></body></html>");
1303         out.close();
1304
1305       } catch (Exception ex)
1306       {
1307         ex.printStackTrace();
1308       }
1309     } // /////////END OF IMAGE MAP
1310
1311   }
1312
1313   int getWrappedHeight()
1314   {
1315     int seqPanelWidth = seqPanel.seqCanvas.getWidth();
1316
1317     if (System.getProperty("java.awt.headless") != null
1318             && System.getProperty("java.awt.headless").equals("true"))
1319     {
1320       seqPanelWidth = alignFrame.getWidth() - getVisibleIdWidth()
1321               - vscroll.getPreferredSize().width
1322               - alignFrame.getInsets().left - alignFrame.getInsets().right;
1323     }
1324
1325     int chunkWidth = seqPanel.seqCanvas
1326             .getWrappedCanvasWidth(seqPanelWidth);
1327
1328     int hgap = av.charHeight;
1329     if (av.scaleAboveWrapped)
1330     {
1331       hgap += av.charHeight;
1332     }
1333
1334     int annotationHeight = 0;
1335     if (av.showAnnotation)
1336     {
1337       annotationHeight = annotationPanel.adjustPanelHeight();
1338     }
1339
1340     int cHeight = av.getAlignment().getHeight() * av.charHeight + hgap
1341             + annotationHeight;
1342
1343     int maxwidth = av.alignment.getWidth();
1344     if (av.hasHiddenColumns)
1345     {
1346       maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
1347     }
1348
1349     int height = ((maxwidth / chunkWidth) + 1) * cHeight;
1350
1351     return height;
1352   }
1353
1354   /**
1355    * close the panel - deregisters all listeners and nulls any references to
1356    * alignment data.
1357    */
1358   public void closePanel()
1359   {
1360     PaintRefresher.RemoveComponent(seqPanel.seqCanvas);
1361     PaintRefresher.RemoveComponent(idPanel.idCanvas);
1362     PaintRefresher.RemoveComponent(this);
1363     if (av != null)
1364     {
1365       jalview.structure.StructureSelectionManager ssm = av.getStructureSelectionManager();
1366       ssm.removeStructureViewerListener(seqPanel, null);
1367       ssm.removeSelectionListener(seqPanel);
1368       av.alignment = null;
1369       av = null;
1370     }
1371     else
1372     {
1373       if (Cache.log.isDebugEnabled())
1374       {
1375         Cache.log.warn("Closing alignment panel which is already closed.");
1376       }
1377     }
1378   }
1379
1380   /**
1381    * hides or shows dynamic annotation rows based on groups and av state flags
1382    */
1383   public void updateAnnotation()
1384   {
1385     updateAnnotation(false);
1386   }
1387
1388   public void updateAnnotation(boolean applyGlobalSettings)
1389   {
1390     // TODO: this should be merged with other annotation update stuff - that
1391     // sits on AlignViewport
1392     boolean updateCalcs = false;
1393     boolean conv = av.isShowGroupConservation();
1394     boolean cons = av.isShowGroupConsensus();
1395     boolean showprf = av.isShowSequenceLogo();
1396     boolean showConsHist = av.isShowConsensusHistogram();
1397
1398     boolean sortg = true;
1399
1400     // remove old automatic annotation
1401     // add any new annotation
1402
1403     Vector gr = av.alignment.getGroups(); // OrderedBy(av.alignment.getSequencesArray());
1404     // intersect alignment annotation with alignment groups
1405
1406     AlignmentAnnotation[] aan = av.alignment.getAlignmentAnnotation();
1407     Hashtable oldrfs = new Hashtable();
1408     if (aan != null)
1409     {
1410       for (int an = 0; an < aan.length; an++)
1411       {
1412         if (aan[an].autoCalculated && aan[an].groupRef != null)
1413         {
1414           oldrfs.put(aan[an].groupRef, aan[an].groupRef);
1415           av.alignment.deleteAnnotation(aan[an]);
1416           aan[an] = null;
1417         }
1418       }
1419     }
1420     SequenceGroup sg;
1421     if (gr != null)
1422     {
1423       for (int g = 0; g < gr.size(); g++)
1424       {
1425         updateCalcs = false;
1426         sg = (SequenceGroup) gr.elementAt(g);
1427         if (applyGlobalSettings || !oldrfs.containsKey(sg))
1428         {
1429           // set defaults for this group's conservation/consensus
1430           sg.setshowSequenceLogo(showprf);
1431           sg.setShowConsensusHistogram(showConsHist);
1432         }
1433         if (conv)
1434         {
1435           updateCalcs = true;
1436           av.alignment.addAnnotation(sg.getConservationRow(), 0);
1437         }
1438         if (cons)
1439         {
1440           updateCalcs = true;
1441           av.alignment.addAnnotation(sg.getConsensus(), 0);
1442         }
1443         // refresh the annotation rows
1444         if (updateCalcs)
1445         {
1446           sg.recalcConservation();
1447         }
1448       }
1449     }
1450     oldrfs.clear();
1451     adjustAnnotationHeight();
1452   }
1453
1454   @Override
1455   public AlignmentI getAlignment()
1456   {
1457     return av.alignment;
1458   }
1459
1460   /**
1461    * get the name for this view
1462    * @return 
1463    */
1464   public String getViewName()
1465   {
1466     return av.viewName;
1467   }
1468
1469   /**
1470    * Make/Unmake this alignment panel the current input focus
1471    * @param b
1472    */
1473   public void setSelected(boolean b)
1474   {
1475     try {
1476       alignFrame.setSelected(b);
1477       } catch (Exception ex) {};
1478       
1479     if (b)
1480     {
1481       alignFrame.setDisplayedView(this);
1482     } 
1483   }
1484
1485   @Override
1486   public StructureSelectionManager getStructureSelectionManager()
1487   {
1488     return av.getStructureSelectionManager();
1489   }
1490 }