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