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