2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
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.
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.
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.
23 import jalview.datamodel.AlignmentAnnotation;
24 import jalview.datamodel.AlignmentI;
25 import jalview.datamodel.Annotation;
26 import jalview.datamodel.ColumnSelection;
27 import jalview.datamodel.HiddenColumns;
28 import jalview.datamodel.SequenceI;
29 import jalview.renderer.AnnotationRenderer;
30 import jalview.renderer.AwtRenderPanelI;
31 import jalview.schemes.ResidueProperties;
32 import jalview.util.Comparison;
33 import jalview.util.MessageManager;
34 import jalview.util.Platform;
35 import jalview.viewmodel.ViewportListenerI;
36 import jalview.viewmodel.ViewportRanges;
38 import java.awt.AlphaComposite;
39 import java.awt.Color;
40 import java.awt.Dimension;
41 import java.awt.FontMetrics;
42 import java.awt.Graphics;
43 import java.awt.Graphics2D;
44 import java.awt.Image;
45 import java.awt.Rectangle;
46 import java.awt.RenderingHints;
47 import java.awt.event.ActionEvent;
48 import java.awt.event.ActionListener;
49 import java.awt.event.AdjustmentEvent;
50 import java.awt.event.AdjustmentListener;
51 import java.awt.event.MouseEvent;
52 import java.awt.event.MouseListener;
53 import java.awt.event.MouseMotionListener;
54 import java.awt.event.MouseWheelEvent;
55 import java.awt.event.MouseWheelListener;
56 import java.awt.image.BufferedImage;
57 import java.beans.PropertyChangeEvent;
58 import java.util.ArrayList;
59 import java.util.Collections;
60 import java.util.List;
62 import javax.swing.JColorChooser;
63 import javax.swing.JMenuItem;
64 import javax.swing.JPanel;
65 import javax.swing.JPopupMenu;
66 import javax.swing.Scrollable;
67 import javax.swing.ToolTipManager;
70 * AnnotationPanel displays visible portion of annotation rows below unwrapped
76 public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
77 MouseListener, MouseWheelListener, MouseMotionListener,
78 ActionListener, AdjustmentListener, Scrollable, ViewportListenerI
82 Select, Resize, Undefined
85 String HELIX = MessageManager.getString("label.helix");
87 String SHEET = MessageManager.getString("label.sheet");
90 * For RNA secondary structure "stems" aka helices
92 String STEM = MessageManager.getString("label.rna_helix");
94 String LABEL = MessageManager.getString("label.label");
96 String REMOVE = MessageManager.getString("label.remove_annotation");
98 String COLOUR = MessageManager.getString("action.colour");
100 public final Color HELIX_COLOUR = Color.red.darker();
102 public final Color SHEET_COLOUR = Color.green.darker().darker();
104 public final Color STEM_COLOUR = Color.blue.darker();
107 public AlignViewport av;
111 public int activeRow = -1;
113 public BufferedImage image;
115 public volatile BufferedImage fadedImage;
119 public FontMetrics fm;
121 public int imgWidth = 0;
123 boolean fastPaint = false;
125 // Used For mouse Dragging and resizing graphs
126 int graphStretch = -1;
128 int mouseDragLastX = -1;
130 int mouseDragLastY = -1;
132 DragMode dragMode = DragMode.Undefined;
134 boolean mouseDragging = false;
136 // for editing cursor
141 public final AnnotationRenderer renderer;
143 private MouseWheelListener[] _mwl;
146 * Creates a new AnnotationPanel object.
151 public AnnotationPanel(AlignmentPanel ap)
153 ToolTipManager.sharedInstance().registerComponent(this);
154 ToolTipManager.sharedInstance().setInitialDelay(0);
155 ToolTipManager.sharedInstance().setDismissDelay(10000);
158 this.setLayout(null);
159 addMouseListener(this);
160 addMouseMotionListener(this);
161 ap.annotationScroller.getVerticalScrollBar()
162 .addAdjustmentListener(this);
163 // save any wheel listeners on the scroller, so we can propagate scroll
165 _mwl = ap.annotationScroller.getMouseWheelListeners();
166 // and then set our own listener to consume all mousewheel events
167 ap.annotationScroller.addMouseWheelListener(this);
168 renderer = new AnnotationRenderer();
170 av.getRanges().addPropertyChangeListener(this);
173 public AnnotationPanel(AlignViewport av)
176 renderer = new AnnotationRenderer();
180 public void mouseWheelMoved(MouseWheelEvent e)
185 double wheelRotation = e.getPreciseWheelRotation();
186 if (wheelRotation > 0)
188 av.getRanges().scrollRight(true);
190 else if (wheelRotation < 0)
192 av.getRanges().scrollRight(false);
197 // TODO: find the correct way to let the event bubble up to
198 // ap.annotationScroller
199 for (MouseWheelListener mwl : _mwl)
203 mwl.mouseWheelMoved(e);
214 public Dimension getPreferredScrollableViewportSize()
216 Dimension ps = getPreferredSize();
217 return new Dimension(ps.width, adjustForAlignFrame(false, ps.height));
221 public int getScrollableBlockIncrement(Rectangle visibleRect,
222 int orientation, int direction)
228 public boolean getScrollableTracksViewportHeight()
234 public boolean getScrollableTracksViewportWidth()
240 public int getScrollableUnitIncrement(Rectangle visibleRect,
241 int orientation, int direction)
250 * java.awt.event.AdjustmentListener#adjustmentValueChanged(java.awt.event
254 public void adjustmentValueChanged(AdjustmentEvent evt)
256 // update annotation label display
257 ap.getAlabels().setScrollOffset(-evt.getValue());
261 * Calculates the height of the annotation displayed in the annotation panel.
262 * Callers should normally call the ap.adjustAnnotationHeight method to ensure
263 * all annotation associated components are updated correctly.
266 public int adjustPanelHeight()
268 int height = av.calcPanelHeight();
269 this.setPreferredSize(new Dimension(1, height));
272 // revalidate only when the alignment panel is fully constructed
286 public void actionPerformed(ActionEvent evt)
288 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
293 Annotation[] anot = aa[activeRow].annotations;
295 if (anot.length < av.getColumnSelection().getMax())
297 Annotation[] temp = new Annotation[av.getColumnSelection().getMax()
299 System.arraycopy(anot, 0, temp, 0, anot.length);
301 aa[activeRow].annotations = anot;
304 String action = evt.getActionCommand();
305 if (action.equals(REMOVE))
307 for (int index : av.getColumnSelection().getSelected())
309 if (av.getAlignment().getHiddenColumns().isVisible(index))
315 else if (action.equals(LABEL))
317 String exMesg = collectAnnotVals(anot, LABEL);
318 String label = JvOptionPane.showInputDialog(this,
319 MessageManager.getString("label.enter_label"), exMesg);
326 if ((label.length() > 0) && !aa[activeRow].hasText)
328 aa[activeRow].hasText = true;
331 for (int index : av.getColumnSelection().getSelected())
333 if (!av.getAlignment().getHiddenColumns().isVisible(index))
338 if (anot[index] == null)
340 anot[index] = new Annotation(label, "", ' ', 0);
344 anot[index].displayCharacter = label;
348 else if (action.equals(COLOUR))
350 Color col = JColorChooser.showDialog(this,
351 MessageManager.getString("label.select_foreground_colour"),
354 for (int index : av.getColumnSelection().getSelected())
356 if (!av.getAlignment().getHiddenColumns().isVisible(index))
361 if (anot[index] == null)
363 anot[index] = new Annotation("", "", ' ', 0);
366 anot[index].colour = col;
370 // HELIX, SHEET or STEM
373 String symbol = "\u03B1"; // alpha
375 if (action.equals(HELIX))
379 else if (action.equals(SHEET))
382 symbol = "\u03B2"; // beta
385 // Added by LML to color stems
386 else if (action.equals(STEM))
389 int column = av.getColumnSelection().getSelectedRanges().get(0)[0];
390 symbol = aa[activeRow].getDefaultRnaHelixSymbol(column);
393 if (!aa[activeRow].hasIcons)
395 aa[activeRow].hasIcons = true;
398 String label = JvOptionPane.showInputDialog(MessageManager
399 .getString("label.enter_label_for_the_structure"), symbol);
406 if ((label.length() > 0) && !aa[activeRow].hasText)
408 aa[activeRow].hasText = true;
409 if (action.equals(STEM))
411 aa[activeRow].showAllColLabels = true;
414 for (int index : av.getColumnSelection().getSelected())
416 if (!av.getAlignment().getHiddenColumns().isVisible(index))
421 if (anot[index] == null)
423 anot[index] = new Annotation(label, "", type, 0);
426 anot[index].secondaryStructure = type != 'S' ? type
427 : label.length() == 0 ? ' ' : label.charAt(0);
428 anot[index].displayCharacter = label;
433 av.getAlignment().validateAnnotation(aa[activeRow]);
434 ap.alignmentChanged();
435 ap.alignFrame.setMenusForViewport();
443 * Returns any existing annotation concatenated as a string. For each
444 * annotation, takes the description, if any, else the secondary structure
445 * character (if type is HELIX, SHEET or STEM), else the display character (if
452 private String collectAnnotVals(Annotation[] anots, String type)
454 // TODO is this method wanted? why? 'last' is not used
456 StringBuilder collatedInput = new StringBuilder(64);
458 ColumnSelection viscols = av.getColumnSelection();
459 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
462 * the selection list (read-only view) is in selection order, not
463 * column order; make a copy so we can sort it
465 List<Integer> selected = new ArrayList<>(viscols.getSelected());
466 Collections.sort(selected);
467 for (int index : selected)
469 // always check for current display state - just in case
470 if (!hidden.isVisible(index))
474 String tlabel = null;
475 if (anots[index] != null)
476 { // LML added stem code
477 if (type.equals(HELIX) || type.equals(SHEET) || type.equals(STEM)
478 || type.equals(LABEL))
480 tlabel = anots[index].description;
481 if (tlabel == null || tlabel.length() < 1)
483 if (type.equals(HELIX) || type.equals(SHEET)
484 || type.equals(STEM))
486 tlabel = "" + anots[index].secondaryStructure;
490 tlabel = "" + anots[index].displayCharacter;
494 if (tlabel != null && !tlabel.equals(last))
496 if (last.length() > 0)
498 collatedInput.append(" ");
500 collatedInput.append(tlabel);
504 return collatedInput.toString();
508 * Action on right mouse pressed on Mac is to show a pop-up menu for the
509 * annotation. Action on left mouse pressed is to find which annotation is
510 * pressed and mark the start of a column selection or graph resize operation.
515 public void mousePressed(MouseEvent evt)
518 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
523 mouseDragLastX = evt.getX();
524 mouseDragLastY = evt.getY();
527 * add visible annotation heights until we reach the y
528 * position, to find which annotation it is in
533 final int y = evt.getY();
534 for (int i = 0; i < aa.length; i++)
538 height += aa[i].height;
547 else if (aa[i].graph > 0)
550 * we have clicked on a resizable graph annotation
559 * isPopupTrigger fires in mousePressed on Mac,
560 * not until mouseRelease on Windows
562 if (evt.isPopupTrigger() && activeRow != -1)
564 showPopupMenu(y, evt.getX());
568 ap.getScalePanel().mousePressed(evt);
572 * Construct and display a context menu at the right-click position
577 void showPopupMenu(final int y, int x)
579 if (av.getColumnSelection() == null
580 || av.getColumnSelection().isEmpty())
585 JPopupMenu pop = new JPopupMenu(
586 MessageManager.getString("label.structure_type"));
589 * Just display the needed structure options
591 if (av.getAlignment().isNucleotide())
593 item = new JMenuItem(STEM);
594 item.addActionListener(this);
599 item = new JMenuItem(HELIX);
600 item.addActionListener(this);
602 item = new JMenuItem(SHEET);
603 item.addActionListener(this);
606 item = new JMenuItem(LABEL);
607 item.addActionListener(this);
609 item = new JMenuItem(COLOUR);
610 item.addActionListener(this);
612 item = new JMenuItem(REMOVE);
613 item.addActionListener(this);
615 pop.show(this, x, y);
619 * Action on mouse up is to clear mouse drag data and call mouseReleased on
620 * ScalePanel, to deal with defining the selection group (if any) defined by
626 public void mouseReleased(MouseEvent evt)
631 mouseDragging = false;
632 if (dragMode == DragMode.Resize)
634 ap.adjustAnnotationHeight();
636 dragMode = DragMode.Undefined;
637 ap.getScalePanel().mouseReleased(evt);
640 * isPopupTrigger is set in mouseReleased on Windows
641 * (in mousePressed on Mac)
643 if (evt.isPopupTrigger() && activeRow != -1)
645 showPopupMenu(evt.getY(), evt.getX());
657 public void mouseEntered(MouseEvent evt)
659 ap.getScalePanel().mouseEntered(evt);
669 public void mouseExited(MouseEvent evt)
671 ap.getScalePanel().mouseExited(evt);
675 * Action on starting or continuing a mouse drag. There are two possible
678 * <li>drag up or down on a graphed annotation increases or decreases the
679 * height of the graph</li>
680 * <li>dragging left or right selects the columns dragged across</li>
682 * A drag on a graph annotation is treated as column selection if it starts
683 * with more horizontal than vertical movement, and as resize if it starts
684 * with more vertical than horizontal movement. Once started, the drag does
690 public void mouseDragged(MouseEvent evt)
693 * if dragMode is Undefined:
694 * - set to Select if dx > dy
695 * - set to Resize if dy > dx
696 * - do nothing if dx == dy
698 final int x = evt.getX();
699 final int y = evt.getY();
700 if (dragMode == DragMode.Undefined)
702 int dx = Math.abs(x - mouseDragLastX);
703 int dy = Math.abs(y - mouseDragLastY);
704 if (graphStretch == -1 || dx > dy)
707 * mostly horizontal drag, or not a graph annotation
709 dragMode = DragMode.Select;
714 * mostly vertical drag
716 dragMode = DragMode.Resize;
720 if (dragMode == DragMode.Undefined)
723 * drag is diagonal - defer deciding whether to
724 * treat as up/down or left/right
731 if (dragMode == DragMode.Resize)
734 * resize graph annotation if mouse was dragged up or down
736 int deltaY = mouseDragLastY - evt.getY();
739 AlignmentAnnotation graphAnnotation = av.getAlignment()
740 .getAlignmentAnnotation()[graphStretch];
741 int newHeight = Math.max(0, graphAnnotation.graphHeight + deltaY);
742 graphAnnotation.graphHeight = newHeight;
744 ap.paintAlignment(false, false);
750 * for mouse drag left or right, delegate to
751 * ScalePanel to adjust the column selection
753 ap.getScalePanel().mouseDragged(evt);
763 * Constructs the tooltip, and constructs and displays a status message, for
764 * the current mouse position
769 public void mouseMoved(MouseEvent evt)
771 int yPos = evt.getY();
772 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
774 int row = getRowIndex(yPos, aa);
778 this.setToolTipText(null);
782 int column = (evt.getX() / av.getCharWidth())
783 + av.getRanges().getStartRes();
784 column = Math.min(column, av.getRanges().getEndRes());
786 if (av.hasHiddenColumns())
788 column = av.getAlignment().getHiddenColumns()
789 .visibleToAbsoluteColumn(column);
792 AlignmentAnnotation ann = aa[row];
793 if (row > -1 && ann.annotations != null
794 && column < ann.annotations.length)
796 setToolTipText(buildToolTip(ann, column, aa));
797 String msg = getStatusMessage(av.getAlignment(), column, ann);
798 ap.alignFrame.setStatus(msg);
802 this.setToolTipText(null);
803 ap.alignFrame.setStatus(" ");
808 * Answers the index in the annotations array of the visible annotation at the
809 * given y position. This is done by adding the heights of visible annotations
810 * until the y position has been exceeded. Answers -1 if no annotations are
811 * visible, or the y position is below all annotations.
817 static int getRowIndex(int yPos, AlignmentAnnotation[] aa)
826 for (int i = 0; i < aa.length; i++)
830 height += aa[i].height;
843 * Answers a tooltip for the annotation at the current mouse position
849 static String buildToolTip(AlignmentAnnotation ann, int column,
850 AlignmentAnnotation[] anns)
852 String tooltip = null;
853 if (ann.graphGroup > -1)
855 StringBuilder tip = new StringBuilder(32);
856 tip.append("<html>");
857 for (int i = 0; i < anns.length; i++)
859 if (anns[i].graphGroup == ann.graphGroup
860 && anns[i].annotations[column] != null)
862 tip.append(anns[i].label);
863 String description = anns[i].annotations[column].description;
864 if (description != null && description.length() > 0)
866 tip.append(" ").append(description);
871 if (tip.length() != 6)
873 tip.setLength(tip.length() - 4);
874 tooltip = tip.toString() + "</html>";
877 else if (column < ann.annotations.length
878 && ann.annotations[column] != null)
880 String description = ann.annotations[column].description;
881 if (description != null && description.length() > 0)
883 tooltip = JvSwingUtils.wrapTooltip(true, description);
887 tooltip = null; // no tooltip if null or empty description
892 // clear the tooltip.
899 * Constructs and returns the status bar message
905 static String getStatusMessage(AlignmentI al, int column,
906 AlignmentAnnotation ann)
909 * show alignment column and annotation description if any
911 StringBuilder text = new StringBuilder(32);
912 text.append(MessageManager.getString("label.column")).append(" ")
915 if (column < ann.annotations.length && ann.annotations[column] != null)
917 String description = ann.annotations[column].description;
918 if (description != null && description.trim().length() > 0)
920 text.append(" ").append(description);
925 * if the annotation is sequence-specific, show the sequence number
926 * in the alignment, and (if not a gap) the residue and position
928 SequenceI seqref = ann.sequenceRef;
931 int seqIndex = al.findIndex(seqref);
934 text.append(", ").append(MessageManager.getString("label.sequence"))
935 .append(" ").append(seqIndex + 1);
936 char residue = seqref.getCharAt(column);
937 if (!Comparison.isGap(residue))
941 if (al.isNucleotide())
943 name = ResidueProperties.nucleotideName
944 .get(String.valueOf(residue));
945 text.append(" Nucleotide: ")
946 .append(name != null ? name : residue);
950 name = 'X' == residue ? "X"
951 : ('*' == residue ? "STOP"
952 : ResidueProperties.aa2Triplet
953 .get(String.valueOf(residue)));
954 text.append(" Residue: ").append(name != null ? name : residue);
956 int residuePos = seqref.findPosition(column);
957 text.append(" (").append(residuePos).append(")");
962 return text.toString();
972 public void mouseClicked(MouseEvent evt)
974 // if (activeRow != -1)
976 // AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
977 // AlignmentAnnotation anot = aa[activeRow];
981 // TODO mouseClicked-content and drawCursor are quite experimental!
982 public void drawCursor(Graphics graphics, SequenceI seq, int res, int x1,
985 int pady = av.getCharHeight() / 5;
987 graphics.setColor(Color.black);
988 graphics.fillRect(x1, y1, av.getCharWidth(), av.getCharHeight());
990 if (av.validCharWidth)
992 graphics.setColor(Color.white);
994 char s = seq.getCharAt(res);
996 charOffset = (av.getCharWidth() - fm.charWidth(s)) / 2;
997 graphics.drawString(String.valueOf(s), charOffset + x1,
998 (y1 + av.getCharHeight()) - pady);
1003 private volatile boolean imageFresh = false;
1012 public void paintComponent(Graphics g)
1014 super.paintComponent(g);
1016 g.setColor(Color.white);
1017 g.fillRect(0, 0, getWidth(), getHeight());
1021 if (fastPaint || (getVisibleRect().width != g.getClipBounds().width)
1022 || (getVisibleRect().height != g.getClipBounds().height))
1024 g.drawImage(image, 0, 0, this);
1029 imgWidth = (av.getRanges().getEndRes() - av.getRanges().getStartRes()
1030 + 1) * av.getCharWidth();
1035 if (image == null || imgWidth != image.getWidth(this)
1036 || image.getHeight(this) != getHeight())
1040 image = new BufferedImage(imgWidth,
1041 ap.getAnnotationPanel().getHeight(),
1042 BufferedImage.TYPE_INT_RGB);
1043 } catch (OutOfMemoryError oom)
1048 } catch (Exception x)
1053 "Couldn't allocate memory to redraw screen. Please restart Jalview",
1057 gg = (Graphics2D) image.getGraphics();
1061 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
1062 RenderingHints.VALUE_ANTIALIAS_ON);
1065 gg.setFont(av.getFont());
1066 fm = gg.getFontMetrics();
1067 gg.setColor(Color.white);
1068 gg.fillRect(0, 0, imgWidth, image.getHeight());
1072 drawComponent(gg, av.getRanges().getStartRes(),
1073 av.getRanges().getEndRes() + 1);
1075 g.drawImage(image, 0, 0, this);
1079 * set true to enable redraw timing debug output on stderr
1081 private final boolean debugRedraw = false;
1084 * non-Thread safe repaint
1087 * repaint with horizontal shift in alignment
1089 public void fastPaint(int horizontal)
1091 if ((horizontal == 0) || gg == null
1092 || av.getAlignment().getAlignmentAnnotation() == null
1093 || av.getAlignment().getAlignmentAnnotation().length < 1
1094 || av.isCalcInProgress())
1100 int sr = av.getRanges().getStartRes();
1101 int er = av.getRanges().getEndRes() + 1;
1104 gg.copyArea(0, 0, imgWidth, getHeight(),
1105 -horizontal * av.getCharWidth(), 0);
1107 if (horizontal > 0) // scrollbar pulled right, image to the left
1109 transX = (er - sr - horizontal) * av.getCharWidth();
1110 sr = er - horizontal;
1112 else if (horizontal < 0)
1114 er = sr - horizontal;
1117 gg.translate(transX, 0);
1119 drawComponent(gg, sr, er);
1121 gg.translate(-transX, 0);
1125 // Call repaint on alignment panel so that repaints from other alignment
1126 // panel components can be aggregated. Otherwise performance of the overview
1127 // window and others may be adversely affected.
1128 av.getAlignPanel().repaint();
1131 private volatile boolean lastImageGood = false;
1143 public void drawComponent(Graphics g, int startRes, int endRes)
1145 BufferedImage oldFaded = fadedImage;
1146 if (av.isCalcInProgress())
1150 lastImageGood = false;
1153 // We'll keep a record of the old image,
1154 // and draw a faded image until the calculation
1157 && (fadedImage == null || fadedImage.getWidth() != imgWidth
1158 || fadedImage.getHeight() != image.getHeight()))
1160 // System.err.println("redraw faded image ("+(fadedImage==null ?
1161 // "null image" : "") + " lastGood="+lastImageGood+")");
1162 fadedImage = new BufferedImage(imgWidth, image.getHeight(),
1163 BufferedImage.TYPE_INT_RGB);
1165 Graphics2D fadedG = (Graphics2D) fadedImage.getGraphics();
1167 fadedG.setColor(Color.white);
1168 fadedG.fillRect(0, 0, imgWidth, image.getHeight());
1170 fadedG.setComposite(
1171 AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .3f));
1172 fadedG.drawImage(image, 0, 0, this);
1175 // make sure we don't overwrite the last good faded image until all
1176 // calculations have finished
1177 lastImageGood = false;
1182 if (fadedImage != null)
1184 oldFaded = fadedImage;
1189 g.setColor(Color.white);
1190 g.fillRect(0, 0, (endRes - startRes) * av.getCharWidth(), getHeight());
1192 g.setFont(av.getFont());
1195 fm = g.getFontMetrics();
1198 if ((av.getAlignment().getAlignmentAnnotation() == null)
1199 || (av.getAlignment().getAlignmentAnnotation().length < 1))
1201 g.setColor(Color.white);
1202 g.fillRect(0, 0, getWidth(), getHeight());
1203 g.setColor(Color.black);
1204 if (av.validCharWidth)
1206 g.drawString(MessageManager
1207 .getString("label.alignment_has_no_annotations"), 20, 15);
1212 lastImageGood = renderer.drawComponent(this, av, g, activeRow, startRes,
1214 if (!lastImageGood && fadedImage == null)
1216 fadedImage = oldFaded;
1221 public FontMetrics getFontMetrics()
1227 public Image getFadedImage()
1233 public int getFadedImageWidth()
1238 private int[] bounds = new int[2];
1241 public int[] getVisibleVRange()
1243 if (ap != null && ap.getAlabels() != null)
1245 int sOffset = -ap.getAlabels().getScrollOffset();
1246 int visHeight = sOffset + ap.annotationSpaceFillerHolder.getHeight();
1247 bounds[0] = sOffset;
1248 bounds[1] = visHeight;
1258 * Try to ensure any references held are nulled
1260 public void dispose()
1270 * I created the renderer so I will dispose of it
1272 if (renderer != null)
1279 public void propertyChange(PropertyChangeEvent evt)
1281 // Respond to viewport range changes (e.g. alignment panel was scrolled)
1282 // Both scrolling and resizing change viewport ranges: scrolling changes
1283 // both start and end points, but resize only changes end values.
1284 // Here we only want to fastpaint on a scroll, with resize using a normal
1285 // paint, so scroll events are identified as changes to the horizontal or
1286 // vertical start value.
1287 if (evt.getPropertyName().equals(ViewportRanges.STARTRES))
1289 fastPaint((int) evt.getNewValue() - (int) evt.getOldValue());
1291 else if (evt.getPropertyName().equals(ViewportRanges.STARTRESANDSEQ))
1293 fastPaint(((int[]) evt.getNewValue())[0]
1294 - ((int[]) evt.getOldValue())[0]);
1296 else if (evt.getPropertyName().equals(ViewportRanges.MOVE_VIEWPORT))
1303 * computes the visible height of the annotation panel
1305 * @param adjustPanelHeight
1306 * - when false, just adjust existing height according to other
1308 * @param annotationHeight
1309 * @return height to use for the ScrollerPreferredVisibleSize
1311 public int adjustForAlignFrame(boolean adjustPanelHeight,
1312 int annotationHeight)
1315 * Estimate available height in the AlignFrame for alignment +
1316 * annotations. Deduct an estimate for title bar, menu bar, scale panel,
1317 * hscroll, status bar, insets.
1319 int stuff = (ap.getViewName() != null ? 30 : 0)
1320 + (Platform.isAMac() ? 120 : 140);
1321 int availableHeight = ap.alignFrame.getHeight() - stuff;
1322 int rowHeight = av.getCharHeight();
1324 if (adjustPanelHeight)
1326 int alignmentHeight = rowHeight * av.getAlignment().getHeight();
1329 * If not enough vertical space, maximize annotation height while keeping
1330 * at least two rows of alignment visible
1332 if (annotationHeight + alignmentHeight > availableHeight)
1334 annotationHeight = Math.min(annotationHeight,
1335 availableHeight - 2 * rowHeight);
1340 // maintain same window layout whilst updating sliders
1341 annotationHeight = Math.min(ap.annotationScroller.getSize().height,
1342 availableHeight - 2 * rowHeight);
1344 return annotationHeight;