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