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