JAL-1152 with sticky annotation sort order that updates as sequences are
[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     new AnnotationSorter(getAlignment()).sort(getAlignment()
745             .getAlignmentAnnotation(),
746             av.getSortAnnotationsBy());
747     repaint();
748
749     if (updateOverview)
750     {
751       av.getStructureSelectionManager().sequenceColoursChanged(this);
752
753       if (overviewPanel != null)
754       {
755         overviewPanel.updateOverviewImage();
756       }
757     }
758   }
759
760   /**
761    * DOCUMENT ME!
762    * 
763    * @param g
764    *          DOCUMENT ME!
765    */
766   public void paintComponent(Graphics g)
767   {
768     invalidate();
769
770     Dimension d = idPanel.idCanvas.getPreferredSize();
771     idPanelHolder.setPreferredSize(d);
772     hscrollFillerPanel.setPreferredSize(new Dimension(d.width, 12));
773     validate();
774
775     if (av.getWrapAlignment())
776     {
777       int maxwidth = av.getAlignment().getWidth();
778
779       if (av.hasHiddenColumns())
780       {
781         maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
782       }
783
784       int canvasWidth = seqPanel.seqCanvas
785               .getWrappedCanvasWidth(seqPanel.seqCanvas.getWidth());
786       if (canvasWidth > 0)
787       {
788         int max = maxwidth
789                 / seqPanel.seqCanvas
790                         .getWrappedCanvasWidth(seqPanel.seqCanvas
791                                 .getWidth()) + 1;
792         vscroll.setMaximum(max);
793         vscroll.setUnitIncrement(1);
794         vscroll.setVisibleAmount(1);
795       }
796     }
797     else
798     {
799       setScrollValues(av.getStartRes(), av.getStartSeq());
800     }
801   }
802
803   /**
804    * DOCUMENT ME!
805    * 
806    * @param pg
807    *          DOCUMENT ME!
808    * @param pf
809    *          DOCUMENT ME!
810    * @param pi
811    *          DOCUMENT ME!
812    * 
813    * @return DOCUMENT ME!
814    * 
815    * @throws PrinterException
816    *           DOCUMENT ME!
817    */
818   public int print(Graphics pg, PageFormat pf, int pi)
819           throws PrinterException
820   {
821     pg.translate((int) pf.getImageableX(), (int) pf.getImageableY());
822
823     int pwidth = (int) pf.getImageableWidth();
824     int pheight = (int) pf.getImageableHeight();
825
826     if (av.getWrapAlignment())
827     {
828       return printWrappedAlignment(pg, pwidth, pheight, pi);
829     }
830     else
831     {
832       return printUnwrapped(pg, pwidth, pheight, pi);
833     }
834   }
835
836   /**
837    * DOCUMENT ME!
838    * 
839    * @param pg
840    *          DOCUMENT ME!
841    * @param pwidth
842    *          DOCUMENT ME!
843    * @param pheight
844    *          DOCUMENT ME!
845    * @param pi
846    *          DOCUMENT ME!
847    * 
848    * @return DOCUMENT ME!
849    * 
850    * @throws PrinterException
851    *           DOCUMENT ME!
852    */
853   public int printUnwrapped(Graphics pg, int pwidth, int pheight, int pi)
854           throws PrinterException
855   {
856     int idWidth = getVisibleIdWidth(false);
857     FontMetrics fm = getFontMetrics(av.getFont());
858     int scaleHeight = av.charHeight + fm.getDescent();
859
860     pg.setColor(Color.white);
861     pg.fillRect(0, 0, pwidth, pheight);
862     pg.setFont(av.getFont());
863
864     // //////////////////////////////////
865     // / How many sequences and residues can we fit on a printable page?
866     int totalRes = (pwidth - idWidth) / av.getCharWidth();
867
868     int totalSeq = (pheight - scaleHeight) / av.getCharHeight() - 1;
869
870     int pagesWide = (av.getAlignment().getWidth() / totalRes) + 1;
871
872     // ///////////////////////////
873     // / Only print these sequences and residues on this page
874     int startRes;
875
876     // ///////////////////////////
877     // / Only print these sequences and residues on this page
878     int endRes;
879
880     // ///////////////////////////
881     // / Only print these sequences and residues on this page
882     int startSeq;
883
884     // ///////////////////////////
885     // / Only print these sequences and residues on this page
886     int endSeq;
887     startRes = (pi % pagesWide) * totalRes;
888     endRes = (startRes + totalRes) - 1;
889
890     if (endRes > (av.getAlignment().getWidth() - 1))
891     {
892       endRes = av.getAlignment().getWidth() - 1;
893     }
894
895     startSeq = (pi / pagesWide) * totalSeq;
896     endSeq = startSeq + totalSeq;
897
898     if (endSeq > av.getAlignment().getHeight())
899     {
900       endSeq = av.getAlignment().getHeight();
901     }
902
903     int pagesHigh = ((av.getAlignment().getHeight() / totalSeq) + 1)
904             * pheight;
905
906     if (av.showAnnotation)
907     {
908       pagesHigh += annotationPanel.adjustPanelHeight() + 3;
909     }
910
911     pagesHigh /= pheight;
912
913     if (pi >= (pagesWide * pagesHigh))
914     {
915       return Printable.NO_SUCH_PAGE;
916     }
917
918     // draw Scale
919     pg.translate(idWidth, 0);
920     scalePanel.drawScale(pg, startRes, endRes, pwidth - idWidth,
921             scaleHeight);
922     pg.translate(-idWidth, scaleHeight);
923
924     // //////////////
925     // Draw the ids
926     Color currentColor = null;
927     Color currentTextColor = null;
928
929     pg.setFont(idPanel.idCanvas.idfont);
930
931     SequenceI seq;
932     for (int i = startSeq; i < endSeq; i++)
933     {
934       seq = av.getAlignment().getSequenceAt(i);
935       if ((av.getSelectionGroup() != null)
936               && av.getSelectionGroup().getSequences(null).contains(seq))
937       {
938         currentColor = Color.gray;
939         currentTextColor = Color.black;
940       }
941       else
942       {
943         currentColor = av.getSequenceColour(seq);
944         currentTextColor = Color.black;
945       }
946
947       pg.setColor(currentColor);
948       pg.fillRect(0, (i - startSeq) * av.charHeight, idWidth,
949               av.getCharHeight());
950
951       pg.setColor(currentTextColor);
952
953       int xPos = 0;
954       if (av.rightAlignIds)
955       {
956         fm = pg.getFontMetrics();
957         xPos = idWidth
958                 - fm.stringWidth(seq.getDisplayId(av.getShowJVSuffix()))
959                 - 4;
960       }
961
962       pg.drawString(
963               seq.getDisplayId(av.getShowJVSuffix()),
964               xPos,
965               (((i - startSeq) * av.charHeight) + av.getCharHeight())
966                       - (av.getCharHeight() / 5));
967     }
968
969     pg.setFont(av.getFont());
970
971     // draw main sequence panel
972     pg.translate(idWidth, 0);
973     seqPanel.seqCanvas.drawPanel(pg, startRes, endRes, startSeq, endSeq, 0);
974
975     if (av.showAnnotation && (endSeq == av.getAlignment().getHeight()))
976     {
977       // draw annotation - need to offset for current scroll position
978       int offset = -alabels.scrollOffset;
979       pg.translate(0, offset);
980       pg.translate(-idWidth - 3, (endSeq - startSeq) * av.charHeight + 3);
981       alabels.drawComponent(pg, idWidth);
982       pg.translate(idWidth + 3, 0);
983       annotationPanel.renderer.drawComponent(annotationPanel, av,
984               pg, -1, startRes, endRes + 1);
985       pg.translate(0, -offset);
986     }
987
988     return Printable.PAGE_EXISTS;
989   }
990
991   /**
992    * DOCUMENT ME!
993    * 
994    * @param pg
995    *          DOCUMENT ME!
996    * @param pwidth
997    *          DOCUMENT ME!
998    * @param pheight
999    *          DOCUMENT ME!
1000    * @param pi
1001    *          DOCUMENT ME!
1002    * 
1003    * @return DOCUMENT ME!
1004    * 
1005    * @throws PrinterException
1006    *           DOCUMENT ME!
1007    */
1008   public int printWrappedAlignment(Graphics pg, int pwidth, int pheight,
1009           int pi) throws PrinterException
1010   {
1011
1012     int annotationHeight = 0;
1013     AnnotationLabels labels = null;
1014     if (av.showAnnotation)
1015     {
1016       annotationHeight = annotationPanel.adjustPanelHeight();
1017       labels = new AnnotationLabels(av);
1018     }
1019
1020     int hgap = av.charHeight;
1021     if (av.scaleAboveWrapped)
1022     {
1023       hgap += av.charHeight;
1024     }
1025
1026     int cHeight = av.getAlignment().getHeight() * av.charHeight + hgap
1027             + annotationHeight;
1028
1029     int idWidth = getVisibleIdWidth(false);
1030
1031     int maxwidth = av.getAlignment().getWidth();
1032     if (av.hasHiddenColumns())
1033     {
1034       maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
1035     }
1036
1037     int resWidth = seqPanel.seqCanvas.getWrappedCanvasWidth(pwidth
1038             - idWidth);
1039
1040     int totalHeight = cHeight * (maxwidth / resWidth + 1);
1041
1042     pg.setColor(Color.white);
1043     pg.fillRect(0, 0, pwidth, pheight);
1044     pg.setFont(av.getFont());
1045
1046     // //////////////
1047     // Draw the ids
1048     pg.setColor(Color.black);
1049
1050     pg.translate(0, -pi * pheight);
1051
1052     pg.setClip(0, pi * pheight, pwidth, pheight);
1053
1054     int ypos = hgap;
1055
1056     do
1057     {
1058       for (int i = 0; i < av.getAlignment().getHeight(); i++)
1059       {
1060         pg.setFont(idPanel.idCanvas.idfont);
1061         SequenceI s = av.getAlignment().getSequenceAt(i);
1062         String string = s.getDisplayId(av.getShowJVSuffix());
1063         int xPos = 0;
1064         if (av.rightAlignIds)
1065         {
1066           FontMetrics fm = pg.getFontMetrics();
1067           xPos = idWidth - fm.stringWidth(string) - 4;
1068         }
1069         pg.drawString(string, xPos,
1070                 ((i * av.charHeight) + ypos + av.charHeight)
1071                         - (av.charHeight / 5));
1072       }
1073       if (labels != null)
1074       {
1075         pg.translate(-3, ypos
1076                 + (av.getAlignment().getHeight() * av.charHeight));
1077
1078         pg.setFont(av.getFont());
1079         labels.drawComponent(pg, idWidth);
1080         pg.translate(+3, -ypos
1081                 - (av.getAlignment().getHeight() * av.charHeight));
1082       }
1083
1084       ypos += cHeight;
1085     } while (ypos < totalHeight);
1086
1087     pg.translate(idWidth, 0);
1088
1089     seqPanel.seqCanvas.drawWrappedPanel(pg, pwidth - idWidth, totalHeight,
1090             0);
1091
1092     if ((pi * pheight) < totalHeight)
1093     {
1094       return Printable.PAGE_EXISTS;
1095
1096     }
1097     else
1098     {
1099       return Printable.NO_SUCH_PAGE;
1100     }
1101   }
1102
1103   /**
1104    * get current sequence ID panel width, or nominal value if panel were to be
1105    * displayed using default settings
1106    * 
1107    * @return
1108    */
1109   int getVisibleIdWidth()
1110   {
1111     return getVisibleIdWidth(true);
1112   }
1113
1114   /**
1115    * get current sequence ID panel width, or nominal value if panel were to be
1116    * displayed using default settings
1117    * 
1118    * @param onscreen
1119    *          indicate if the Id width for onscreen or offscreen display should
1120    *          be returned
1121    * @return
1122    */
1123   int getVisibleIdWidth(boolean onscreen)
1124   {
1125     // see if rendering offscreen - check preferences and calc width accordingly
1126     if (!onscreen && Cache.getDefault("FIGURE_AUTOIDWIDTH", false))
1127     {
1128       return calculateIdWidth(-1).width + 4;
1129     }
1130     Integer idwidth = null;
1131     if (onscreen
1132             || (idwidth = Cache.getIntegerProperty("FIGURE_FIXEDIDWIDTH")) == null)
1133     {
1134       return (idPanel.getWidth() > 0 ? idPanel.getWidth()
1135               : calculateIdWidth().width + 4);
1136     }
1137     return idwidth.intValue() + 4;
1138   }
1139
1140   void makeAlignmentImage(int type, File file)
1141   {
1142     long progress = System.currentTimeMillis();
1143     boolean headless = (System.getProperty("java.awt.headless") != null && System
1144             .getProperty("java.awt.headless").equals("true"));
1145     if (alignFrame != null && !headless)
1146     {
1147       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);
1148     }
1149     try
1150     {
1151       int maxwidth = av.getAlignment().getWidth();
1152       if (av.hasHiddenColumns())
1153       {
1154         maxwidth = av.getColumnSelection().findColumnPosition(maxwidth);
1155       }
1156
1157       int height = ((av.getAlignment().getHeight() + 1) * av.charHeight)
1158               + scalePanel.getHeight();
1159       int width = getVisibleIdWidth(false) + (maxwidth * av.charWidth);
1160
1161       if (av.getWrapAlignment())
1162       {
1163         height = getWrappedHeight();
1164         if (headless)
1165         {
1166           // need to obtain default alignment width and then add in any
1167           // additional allowance for id margin
1168           // this duplicates the calculation in getWrappedHeight but adjusts for
1169           // offscreen idWith
1170           width = alignFrame.getWidth() - vscroll.getPreferredSize().width
1171                   - alignFrame.getInsets().left
1172                   - alignFrame.getInsets().right - getVisibleIdWidth()
1173                   + getVisibleIdWidth(false);
1174         }
1175         else
1176         {
1177           width = seqPanel.getWidth() + getVisibleIdWidth(false);
1178         }
1179
1180       }
1181       else if (av.getShowAnnotation())
1182       {
1183         height += annotationPanel.adjustPanelHeight() + 3;
1184       }
1185
1186       try
1187       {
1188
1189         jalview.util.ImageMaker im;
1190         final String imageAction, imageTitle;
1191         if (type == jalview.util.ImageMaker.PNG)
1192         {
1193           imageAction = "Create PNG image from alignment";
1194           imageTitle = null;
1195         }
1196         else
1197         {
1198           imageAction = "Create EPS file from alignment";
1199           imageTitle = alignFrame.getTitle();
1200         }
1201         im = new jalview.util.ImageMaker(this, type, imageAction, width,
1202                 height, file, imageTitle);
1203         if (av.getWrapAlignment())
1204         {
1205           if (im.getGraphics() != null)
1206           {
1207             printWrappedAlignment(im.getGraphics(), width, height, 0);
1208             im.writeImage();
1209           }
1210         }
1211         else
1212         {
1213           if (im.getGraphics() != null)
1214           {
1215             printUnwrapped(im.getGraphics(), width, height, 0);
1216             im.writeImage();
1217           }
1218         }
1219       } catch (OutOfMemoryError err)
1220       {
1221         // Be noisy here.
1222         System.out.println("########################\n" + "OUT OF MEMORY "
1223                 + file + "\n" + "########################");
1224         new OOMWarning("Creating Image for " + file, err);
1225         // System.out.println("Create IMAGE: " + err);
1226       } catch (Exception ex)
1227       {
1228         ex.printStackTrace();
1229       }
1230     } finally
1231     {
1232       if (alignFrame != null && !headless)
1233       {
1234         alignFrame.setProgressBar(MessageManager.getString("status.export_complete"), progress);
1235       }
1236     }
1237   }
1238
1239   /**
1240    * DOCUMENT ME!
1241    */
1242   public void makeEPS(File epsFile)
1243   {
1244     makeAlignmentImage(jalview.util.ImageMaker.EPS, epsFile);
1245   }
1246
1247   /**
1248    * DOCUMENT ME!
1249    */
1250   public void makePNG(File pngFile)
1251   {
1252     makeAlignmentImage(jalview.util.ImageMaker.PNG, pngFile);
1253   }
1254
1255   public void makePNGImageMap(File imgMapFile, String imageName)
1256   {
1257     // /////ONLY WORKS WITH NONE WRAPPED ALIGNMENTS
1258     // ////////////////////////////////////////////
1259     int idWidth = getVisibleIdWidth(false);
1260     FontMetrics fm = getFontMetrics(av.getFont());
1261     int scaleHeight = av.charHeight + fm.getDescent();
1262
1263     // Gen image map
1264     // ////////////////////////////////
1265     if (imgMapFile != null)
1266     {
1267       try
1268       {
1269         int s, sSize = av.getAlignment().getHeight(), res, alwidth = av
1270                 .getAlignment().getWidth(), g, gSize, f, fSize, sy;
1271         StringBuffer text = new StringBuffer();
1272         PrintWriter out = new PrintWriter(new FileWriter(imgMapFile));
1273         out.println(jalview.io.HTMLOutput.getImageMapHTML());
1274         out.println("<img src=\"" + imageName
1275                 + "\" border=\"0\" usemap=\"#Map\" >"
1276                 + "<map name=\"Map\">");
1277
1278         for (s = 0; s < sSize; s++)
1279         {
1280           sy = s * av.charHeight + scaleHeight;
1281
1282           SequenceI seq = av.getAlignment().getSequenceAt(s);
1283           SequenceFeature[] features = seq.getDatasetSequence()
1284                   .getSequenceFeatures();
1285           SequenceGroup[] groups = av.getAlignment().findAllGroups(seq);
1286           for (res = 0; res < alwidth; res++)
1287           {
1288             text = new StringBuffer();
1289             Object obj = null;
1290             if (av.getAlignment().isNucleotide())
1291             {
1292               obj = ResidueProperties.nucleotideName.get(seq.getCharAt(res)
1293                       + "");
1294             }
1295             else
1296             {
1297               obj = ResidueProperties.aa2Triplet.get(seq.getCharAt(res)
1298                       + "");
1299             }
1300
1301             if (obj == null)
1302             {
1303               continue;
1304             }
1305
1306             String triplet = obj.toString();
1307             int alIndex = seq.findPosition(res);
1308             gSize = groups.length;
1309             for (g = 0; g < gSize; g++)
1310             {
1311               if (text.length() < 1)
1312               {
1313                 text.append("<area shape=\"rect\" coords=\""
1314                         + (idWidth + res * av.charWidth) + "," + sy + ","
1315                         + (idWidth + (res + 1) * av.charWidth) + ","
1316                         + (av.charHeight + sy) + "\""
1317                         + " onMouseOver=\"toolTip('" + alIndex + " "
1318                         + triplet);
1319               }
1320
1321               if (groups[g].getStartRes() < res
1322                       && groups[g].getEndRes() > res)
1323               {
1324                 text.append("<br><em>" + groups[g].getName() + "</em>");
1325               }
1326             }
1327
1328             if (features != null)
1329             {
1330               if (text.length() < 1)
1331               {
1332                 text.append("<area shape=\"rect\" coords=\""
1333                         + (idWidth + res * av.charWidth) + "," + sy + ","
1334                         + (idWidth + (res + 1) * av.charWidth) + ","
1335                         + (av.charHeight + sy) + "\""
1336                         + " onMouseOver=\"toolTip('" + alIndex + " "
1337                         + triplet);
1338               }
1339               fSize = features.length;
1340               for (f = 0; f < fSize; f++)
1341               {
1342
1343                 if ((features[f].getBegin() <= seq.findPosition(res))
1344                         && (features[f].getEnd() >= seq.findPosition(res)))
1345                 {
1346                   if (features[f].getType().equals("disulfide bond"))
1347                   {
1348                     if (features[f].getBegin() == seq.findPosition(res)
1349                             || features[f].getEnd() == seq
1350                                     .findPosition(res))
1351                     {
1352                       text.append("<br>disulfide bond "
1353                               + features[f].getBegin() + ":"
1354                               + features[f].getEnd());
1355                     }
1356                   }
1357                   else
1358                   {
1359                     text.append("<br>");
1360                     text.append(features[f].getType());
1361                     if (features[f].getDescription() != null
1362                             && !features[f].getType().equals(
1363                                     features[f].getDescription()))
1364                     {
1365                       text.append(" " + features[f].getDescription());
1366                     }
1367
1368                     if (features[f].getValue("status") != null)
1369                     {
1370                       text.append(" (" + features[f].getValue("status")
1371                               + ")");
1372                     }
1373                   }
1374                 }
1375
1376               }
1377             }
1378             if (text.length() > 1)
1379             {
1380               text.append("')\"; onMouseOut=\"toolTip()\";  href=\"#\">");
1381               out.println(text.toString());
1382             }
1383           }
1384         }
1385         out.println("</map></body></html>");
1386         out.close();
1387
1388       } catch (Exception ex)
1389       {
1390         ex.printStackTrace();
1391       }
1392     } // /////////END OF IMAGE MAP
1393
1394   }
1395
1396   int getWrappedHeight()
1397   {
1398     int seqPanelWidth = seqPanel.seqCanvas.getWidth();
1399
1400     if (System.getProperty("java.awt.headless") != null
1401             && System.getProperty("java.awt.headless").equals("true"))
1402     {
1403       seqPanelWidth = alignFrame.getWidth() - getVisibleIdWidth()
1404               - vscroll.getPreferredSize().width
1405               - alignFrame.getInsets().left - alignFrame.getInsets().right;
1406     }
1407
1408     int chunkWidth = seqPanel.seqCanvas
1409             .getWrappedCanvasWidth(seqPanelWidth);
1410
1411     int hgap = av.charHeight;
1412     if (av.scaleAboveWrapped)
1413     {
1414       hgap += av.charHeight;
1415     }
1416
1417     int annotationHeight = 0;
1418     if (av.showAnnotation)
1419     {
1420       annotationHeight = annotationPanel.adjustPanelHeight();
1421     }
1422
1423     int cHeight = av.getAlignment().getHeight() * av.charHeight + hgap
1424             + annotationHeight;
1425
1426     int maxwidth = av.getAlignment().getWidth();
1427     if (av.hasHiddenColumns())
1428     {
1429       maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
1430     }
1431
1432     int height = ((maxwidth / chunkWidth) + 1) * cHeight;
1433
1434     return height;
1435   }
1436
1437   /**
1438    * close the panel - deregisters all listeners and nulls any references to
1439    * alignment data.
1440    */
1441   public void closePanel()
1442   {
1443     PaintRefresher.RemoveComponent(seqPanel.seqCanvas);
1444     PaintRefresher.RemoveComponent(idPanel.idCanvas);
1445     PaintRefresher.RemoveComponent(this);
1446     if (av != null)
1447     {
1448       jalview.structure.StructureSelectionManager ssm = av
1449               .getStructureSelectionManager();
1450       ssm.removeStructureViewerListener(seqPanel, null);
1451       ssm.removeSelectionListener(seqPanel);
1452       av.setAlignment(null);
1453       av = null;
1454     }
1455     else
1456     {
1457       if (Cache.log.isDebugEnabled())
1458       {
1459         Cache.log.warn("Closing alignment panel which is already closed.");
1460       }
1461     }
1462   }
1463
1464   /**
1465    * hides or shows dynamic annotation rows based on groups and av state flags
1466    */
1467   public void updateAnnotation()
1468   {
1469     updateAnnotation(false, false);
1470   }
1471
1472   public void updateAnnotation(boolean applyGlobalSettings)
1473   {
1474     updateAnnotation(applyGlobalSettings, false);
1475   }
1476
1477   public void updateAnnotation(boolean applyGlobalSettings,
1478           boolean preserveNewGroupSettings)
1479   {
1480     av.updateGroupAnnotationSettings(applyGlobalSettings,
1481             preserveNewGroupSettings);
1482     adjustAnnotationHeight();
1483   }
1484
1485   @Override
1486   public AlignmentI getAlignment()
1487   {
1488     return av.getAlignment();
1489   }
1490
1491   /**
1492    * get the name for this view
1493    * 
1494    * @return
1495    */
1496   public String getViewName()
1497   {
1498     return av.viewName;
1499   }
1500
1501   /**
1502    * Make/Unmake this alignment panel the current input focus
1503    * 
1504    * @param b
1505    */
1506   public void setSelected(boolean b)
1507   {
1508     try
1509     {
1510       alignFrame.setSelected(b);
1511     } catch (Exception ex)
1512     {
1513     }
1514     ;
1515
1516     if (b)
1517     {
1518       alignFrame.setDisplayedView(this);
1519     }
1520   }
1521
1522   @Override
1523   public StructureSelectionManager getStructureSelectionManager()
1524   {
1525     return av.getStructureSelectionManager();
1526   }
1527
1528   @Override
1529   public void raiseOOMWarning(String string, OutOfMemoryError error)
1530   {
1531     new OOMWarning(string, error, this);
1532   }
1533
1534   public FeatureRenderer cloneFeatureRenderer()
1535   {
1536
1537     return new FeatureRenderer(this);
1538   }
1539
1540   public void updateFeatureRenderer(FeatureRenderer fr)
1541   {
1542     fr.transferSettings(seqPanel.seqCanvas.getFeatureRenderer());
1543   }
1544
1545   public void updateFeatureRendererFrom(FeatureRenderer fr)
1546   {
1547     if (seqPanel.seqCanvas.getFeatureRenderer() != null)
1548     {
1549       seqPanel.seqCanvas.getFeatureRenderer().transferSettings(fr);
1550     }
1551   }
1552 }