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