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