update author list in license for (JAL-826)
[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, J Engelhardt, LM Lui, 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     Container c = new Container();
178
179     FontMetrics fm = c.getFontMetrics(new Font(av.font.getName(),
180             Font.ITALIC, av.font.getSize()));
181
182     AlignmentI al = av.getAlignment();
183     int afwidth = (alignFrame != null ? alignFrame.getWidth() : 300);
184     int maxwidth = Math.max(20,
185             Math.min(afwidth - 200, (int) 2 * afwidth / 3));
186     int i = 0;
187     int idWidth = 0;
188     String id;
189
190     while ((i < al.getHeight()) && (al.getSequenceAt(i) != null))
191     {
192       SequenceI s = al.getSequenceAt(i);
193
194       id = s.getDisplayId(av.getShowJVSuffix());
195
196       if (fm.stringWidth(id) > idWidth)
197       {
198         idWidth = fm.stringWidth(id);
199       }
200
201       i++;
202     }
203
204     // Also check annotation label widths
205     i = 0;
206
207     if (al.getAlignmentAnnotation() != null)
208     {
209       fm = c.getFontMetrics(alabels.getFont());
210
211       while (i < al.getAlignmentAnnotation().length)
212       {
213         String label = al.getAlignmentAnnotation()[i].label;
214
215         if (fm.stringWidth(label) > idWidth)
216         {
217           idWidth = fm.stringWidth(label);
218         }
219
220         i++;
221       }
222     }
223
224     return new Dimension(Math.min(maxwidth, idWidth), 12);
225   }
226
227   /**
228    * Highlight the given results on the alignment.
229    * 
230    */
231   public void highlightSearchResults(SearchResults results)
232   {
233     scrollToPosition(results);
234     seqPanel.seqCanvas.highlightSearchResults(results);
235   }
236
237   /**
238    * scroll the view to show the position of the highlighted region in results
239    * (if any) and redraw the overview
240    * 
241    * @param results
242    */
243   public boolean scrollToPosition(SearchResults results)
244   {
245     return scrollToPosition(results, true);
246   }
247
248   /**
249    * scroll the view to show the position of the highlighted region in results
250    * (if any)
251    * 
252    * @param results
253    * @param redrawOverview
254    *          - when set, the overview will be recalculated (takes longer)
255    * @return false if results were not found
256    */
257   public boolean scrollToPosition(SearchResults results,
258           boolean redrawOverview)
259   {
260     int startv, endv, starts, ends, width;
261     // TODO: properly locate search results in view when large numbers of hidden
262     // columns exist before highlighted region
263     // do we need to scroll the panel?
264     // TODO: tons of nullpointereexceptions raised here.
265     if (results != null && results.getSize() > 0 && av != null
266             && av.alignment != null)
267     {
268       int seqIndex = av.alignment.findIndex(results);
269       if (seqIndex == -1)
270       {
271         return false;
272       }
273       SequenceI seq = av.alignment.getSequenceAt(seqIndex);
274       
275       int[] r=results.getResults(seq, 0, av.alignment.getWidth());
276       if (r == null)
277       {
278         return false;
279       }
280       int start = r[0];
281       int end = r[1];
282       // System.err.println("Seq : "+seqIndex+" Scroll to "+start+","+end); //
283       // DEBUG
284       if (start < 0)
285       {
286         return false;
287       }
288       if (end == seq.getEnd())
289       {
290         return false;
291       }
292       if (av.hasHiddenColumns)
293       {
294         start = av.getColumnSelection().findColumnPosition(start);
295         end = av.getColumnSelection().findColumnPosition(end);
296         if (start==end)
297         {
298           if (!av.colSel.isVisible(r[0]))
299           {
300             // don't scroll - position isn't visible
301             return false;
302           }
303         }
304       }
305       if (!av.wrapAlignment)
306       {
307         if ((startv = av.getStartRes()) >= start)
308         {
309           setScrollValues(start - 1, seqIndex);
310         }
311         else if ((endv = av.getEndRes()) <= end)
312         {
313           setScrollValues(startv + 1 + end - endv, seqIndex);
314         }
315         else if ((starts = av.getStartSeq()) > seqIndex)
316         {
317           setScrollValues(av.getStartRes(), seqIndex);
318         }
319         else if ((ends = av.getEndSeq()) <= seqIndex)
320         {
321           setScrollValues(av.getStartRes(), starts + seqIndex - ends + 1);
322         }
323       }
324       else
325       {
326         scrollToWrappedVisible(start);
327       }
328     }
329     if (redrawOverview && overviewPanel != null)
330     {
331       overviewPanel.setBoxPosition();
332     }
333     paintAlignment(redrawOverview);
334     return true;
335   }
336
337   void scrollToWrappedVisible(int res)
338   {
339     int cwidth = seqPanel.seqCanvas
340             .getWrappedCanvasWidth(seqPanel.seqCanvas.getWidth());
341     if (res < av.getStartRes() || res >= (av.getStartRes() + cwidth))
342     {
343       vscroll.setValue((res / cwidth));
344       av.startRes = vscroll.getValue() * cwidth;
345     }
346
347   }
348
349   /**
350    * DOCUMENT ME!
351    * 
352    * @return DOCUMENT ME!
353    */
354   public OverviewPanel getOverviewPanel()
355   {
356     return overviewPanel;
357   }
358
359   /**
360    * DOCUMENT ME!
361    * 
362    * @param op
363    *          DOCUMENT ME!
364    */
365   public void setOverviewPanel(OverviewPanel op)
366   {
367     overviewPanel = op;
368   }
369
370   /**
371    * 
372    * @param b Hide or show annotation panel
373    *          
374    */
375   public void setAnnotationVisible(boolean b)
376   {
377     if (!av.wrapAlignment)
378     {
379       annotationSpaceFillerHolder.setVisible(b);
380       annotationScroller.setVisible(b);
381     }
382     repaint();
383   }
384
385   /**
386    * automatically adjust annotation panel height for new annotation
387    * whilst ensuring the alignment is still visible.
388    */
389   public void adjustAnnotationHeight()
390   {
391     // TODO: display vertical annotation scrollbar if necessary
392     // this is called after loading new annotation onto alignment
393     if (alignFrame.getHeight() == 0)
394     {
395       System.out.println("NEEDS FIXING");
396     }
397     validateAnnotationDimensions(true);
398     addNotify();
399     paintAlignment(true);
400   }
401   /**
402    * calculate the annotation dimensions and refresh slider values accordingly.
403    * need to do repaints/notifys afterwards. 
404    */
405   protected void validateAnnotationDimensions(boolean adjustPanelHeight) {
406     int height = annotationPanel.adjustPanelHeight();
407     
408     if (hscroll.isVisible())
409     {
410       height += hscroll.getPreferredSize().height;
411     }
412     if (height > alignFrame.getHeight() / 2)
413     {
414       height = alignFrame.getHeight() / 2;
415     }
416     if (!adjustPanelHeight)
417     {
418       // maintain same window layout whilst updating sliders
419       height=annotationScroller.getSize().height;
420     }
421     hscroll.addNotify();
422     
423     annotationScroller.setPreferredSize(new Dimension(annotationScroller
424             .getWidth(), height));
425
426     annotationSpaceFillerHolder.setPreferredSize(new Dimension(
427             annotationSpaceFillerHolder.getWidth(), height));
428     annotationScroller.validate();// repaint();
429     annotationScroller.addNotify();
430   }
431
432   /**
433    * DOCUMENT ME!
434    * 
435    * @param wrap
436    *          DOCUMENT ME!
437    */
438   public void setWrapAlignment(boolean wrap)
439   {
440     av.startSeq = 0;
441     scalePanelHolder.setVisible(!wrap);
442     hscroll.setVisible(!wrap);
443     idwidthAdjuster.setVisible(!wrap);
444
445     if (wrap)
446     {
447       annotationScroller.setVisible(false);
448       annotationSpaceFillerHolder.setVisible(false);
449     }
450     else if (av.showAnnotation)
451     {
452       annotationScroller.setVisible(true);
453       annotationSpaceFillerHolder.setVisible(true);
454     }
455
456     idSpaceFillerPanel1.setVisible(!wrap);
457
458     repaint();
459   }
460
461   // return value is true if the scroll is valid
462   public boolean scrollUp(boolean up)
463   {
464     if (up)
465     {
466       if (vscroll.getValue() < 1)
467       {
468         return false;
469       }
470
471       fastPaint = false;
472       vscroll.setValue(vscroll.getValue() - 1);
473     }
474     else
475     {
476       if ((vextent + vscroll.getValue()) >= av.getAlignment().getHeight())
477       {
478         return false;
479       }
480
481       fastPaint = false;
482       vscroll.setValue(vscroll.getValue() + 1);
483     }
484
485     fastPaint = true;
486
487     return true;
488   }
489
490   /**
491    * DOCUMENT ME!
492    * 
493    * @param right
494    *          DOCUMENT ME!
495    * 
496    * @return DOCUMENT ME!
497    */
498   public boolean scrollRight(boolean right)
499   {
500     if (!right)
501     {
502       if (hscroll.getValue() < 1)
503       {
504         return false;
505       }
506
507       fastPaint = false;
508       hscroll.setValue(hscroll.getValue() - 1);
509     }
510     else
511     {
512       if ((hextent + hscroll.getValue()) >= av.getAlignment().getWidth())
513       {
514         return false;
515       }
516
517       fastPaint = false;
518       hscroll.setValue(hscroll.getValue() + 1);
519     }
520
521     fastPaint = true;
522
523     return true;
524   }
525
526   /**
527    * Adjust row/column scrollers to show a visible position in the alignment.
528    * 
529    * @param x visible column to scroll to
530    *          DOCUMENT ME!
531    * @param y visible row to scroll to
532    *          
533    */
534   public void setScrollValues(int x, int y)
535   {
536     // System.err.println("Scroll to "+x+","+y);
537     if (av == null || av.alignment == null)
538     {
539       return;
540     }
541     int width = av.alignment.getWidth();
542     int height = av.alignment.getHeight();
543
544     if (av.hasHiddenColumns)
545     {
546       width = av.getColumnSelection().findColumnPosition(width);
547     }
548
549     av.setEndRes((x + (seqPanel.seqCanvas.getWidth() / av.charWidth)) - 1);
550
551     hextent = seqPanel.seqCanvas.getWidth() / av.charWidth;
552     vextent = seqPanel.seqCanvas.getHeight() / av.charHeight;
553
554     if (hextent > width)
555     {
556       hextent = width;
557     }
558
559     if (vextent > height)
560     {
561       vextent = height;
562     }
563
564     if ((hextent + x) > width)
565     {
566       x = width - hextent;
567     }
568
569     if ((vextent + y) > height)
570     {
571       y = height - vextent;
572     }
573
574     if (y < 0)
575     {
576       y = 0;
577     }
578
579     if (x < 0)
580     {
581       x = 0;
582     }
583
584     hscroll.setValues(x, hextent, 0, width);
585     vscroll.setValues(y, vextent, 0, height);
586   }
587
588   /**
589    * DOCUMENT ME!
590    * 
591    * @param evt
592    *          DOCUMENT ME!
593    */
594   public void adjustmentValueChanged(AdjustmentEvent evt)
595   {
596
597     int oldX = av.getStartRes();
598     int oldY = av.getStartSeq();
599
600     if (evt.getSource() == hscroll)
601     {
602       int x = hscroll.getValue();
603       av.setStartRes(x);
604       av.setEndRes((x + (seqPanel.seqCanvas.getWidth() / av.getCharWidth())) - 1);
605     }
606
607     if (evt.getSource() == vscroll)
608     {
609       int offy = vscroll.getValue();
610
611       if (av.getWrapAlignment())
612       {
613         if (offy > -1)
614         {
615           int rowSize = seqPanel.seqCanvas
616                   .getWrappedCanvasWidth(seqPanel.seqCanvas.getWidth());
617           av.setStartRes(offy * rowSize);
618           av.setEndRes((offy + 1) * rowSize);
619         }
620         else
621         {
622           // This is only called if file loaded is a jar file that
623           // was wrapped when saved and user has wrap alignment true
624           // as preference setting
625           SwingUtilities.invokeLater(new Runnable()
626           {
627             public void run()
628             {
629               setScrollValues(av.getStartRes(), av.getStartSeq());
630             }
631           });
632         }
633       }
634       else
635       {
636         av.setStartSeq(offy);
637         av.setEndSeq(offy
638                 + (seqPanel.seqCanvas.getHeight() / av.getCharHeight()));
639       }
640     }
641
642     if (overviewPanel != null)
643     {
644       overviewPanel.setBoxPosition();
645     }
646
647     int scrollX = av.startRes - oldX;
648     int scrollY = av.startSeq - oldY;
649
650     if (av.getWrapAlignment() || !fastPaint)
651     {
652       repaint();
653     }
654     else
655     {
656       // Make sure we're not trying to draw a panel
657       // larger than the visible window
658       if (scrollX > av.endRes - av.startRes)
659       {
660         scrollX = av.endRes - av.startRes;
661       }
662       else if (scrollX < av.startRes - av.endRes)
663       {
664         scrollX = av.startRes - av.endRes;
665       }
666
667       if (scrollX != 0 || scrollY != 0)
668       {
669         idPanel.idCanvas.fastPaint(scrollY);
670         seqPanel.seqCanvas.fastPaint(scrollX, scrollY);
671         scalePanel.repaint();
672
673         if (av.getShowAnnotation())
674         {
675           annotationPanel.fastPaint(scrollX);
676         }
677       }
678     }
679   }
680
681   public void paintAlignment(boolean updateOverview)
682   {
683     repaint();
684
685     if (updateOverview)
686     {
687       av.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     PaintRefresher.RemoveComponent(seqPanel.seqCanvas);
1324     PaintRefresher.RemoveComponent(idPanel.idCanvas);
1325     PaintRefresher.RemoveComponent(this);
1326     if (av != null)
1327     {
1328       jalview.structure.StructureSelectionManager ssm = av.getStructureSelectionManager();
1329       ssm.removeStructureViewerListener(seqPanel, null);
1330       ssm.removeSelectionListener(seqPanel);
1331       av.alignment = null;
1332       av = null;
1333     }
1334     else
1335     {
1336       if (Cache.log.isDebugEnabled())
1337       {
1338         Cache.log.warn("Closing alignment panel which is already closed.");
1339       }
1340     }
1341   }
1342
1343   /**
1344    * hides or shows dynamic annotation rows based on groups and av state flags
1345    */
1346   public void updateAnnotation()
1347   {
1348     updateAnnotation(false);
1349   }
1350
1351   public void updateAnnotation(boolean applyGlobalSettings)
1352   {
1353     // TODO: this should be merged with other annotation update stuff - that
1354     // sits on AlignViewport
1355     boolean updateCalcs = false;
1356     boolean conv = av.isShowGroupConservation();
1357     boolean cons = av.isShowGroupConsensus();
1358     boolean showprf = av.isShowSequenceLogo();
1359     boolean showConsHist = av.isShowConsensusHistogram();
1360
1361     boolean sortg = true;
1362
1363     // remove old automatic annotation
1364     // add any new annotation
1365
1366     Vector gr = av.alignment.getGroups(); // OrderedBy(av.alignment.getSequencesArray());
1367     // intersect alignment annotation with alignment groups
1368
1369     AlignmentAnnotation[] aan = av.alignment.getAlignmentAnnotation();
1370     Hashtable oldrfs = new Hashtable();
1371     if (aan != null)
1372     {
1373       for (int an = 0; an < aan.length; an++)
1374       {
1375         if (aan[an].autoCalculated && aan[an].groupRef != null)
1376         {
1377           oldrfs.put(aan[an].groupRef, aan[an].groupRef);
1378           av.alignment.deleteAnnotation(aan[an]);
1379           aan[an] = null;
1380         }
1381       }
1382     }
1383     SequenceGroup sg;
1384     if (gr != null)
1385     {
1386       for (int g = 0; g < gr.size(); g++)
1387       {
1388         updateCalcs = false;
1389         sg = (SequenceGroup) gr.elementAt(g);
1390         if (applyGlobalSettings || !oldrfs.containsKey(sg))
1391         {
1392           // set defaults for this group's conservation/consensus
1393           sg.setshowSequenceLogo(showprf);
1394           sg.setShowConsensusHistogram(showConsHist);
1395         }
1396         if (conv)
1397         {
1398           updateCalcs = true;
1399           av.alignment.addAnnotation(sg.getConservationRow(), 0);
1400         }
1401         if (cons)
1402         {
1403           updateCalcs = true;
1404           av.alignment.addAnnotation(sg.getConsensus(), 0);
1405         }
1406         // refresh the annotation rows
1407         if (updateCalcs)
1408         {
1409           sg.recalcConservation();
1410         }
1411       }
1412     }
1413     oldrfs.clear();
1414     adjustAnnotationHeight();
1415   }
1416
1417   @Override
1418   public AlignmentI getAlignment()
1419   {
1420     return av.alignment;
1421   }
1422
1423   /**
1424    * get the name for this view
1425    * @return 
1426    */
1427   public String getViewName()
1428   {
1429     return av.viewName;
1430   }
1431
1432   /**
1433    * Make/Unmake this alignment panel the current input focus
1434    * @param b
1435    */
1436   public void setSelected(boolean b)
1437   {
1438     try {
1439       alignFrame.setSelected(b);
1440       } catch (Exception ex) {};
1441       
1442     if (b)
1443     {
1444       alignFrame.setDisplayedView(this);
1445     } 
1446   }
1447
1448   @Override
1449   public StructureSelectionManager getStructureSelectionManager()
1450   {
1451     return av.getStructureSelectionManager();
1452   }
1453 }