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 java.awt.AlphaComposite;
24 import java.awt.Color;
25 import java.awt.Dimension;
26 import java.awt.FontMetrics;
27 import java.awt.Graphics;
28 import java.awt.Graphics2D;
29 import java.awt.Image;
30 import java.awt.Rectangle;
31 import java.awt.RenderingHints;
32 import java.awt.event.ActionEvent;
33 import java.awt.event.ActionListener;
34 import java.awt.event.AdjustmentEvent;
35 import java.awt.event.AdjustmentListener;
36 import java.awt.event.MouseEvent;
37 import java.awt.event.MouseListener;
38 import java.awt.event.MouseMotionListener;
39 import java.awt.event.MouseWheelEvent;
40 import java.awt.event.MouseWheelListener;
41 import java.awt.image.BufferedImage;
42 import java.beans.PropertyChangeEvent;
43 import java.util.ArrayList;
44 import java.util.Collections;
45 import java.util.List;
47 import javax.swing.JMenuItem;
48 import javax.swing.JPanel;
49 import javax.swing.JPopupMenu;
50 import javax.swing.Scrollable;
51 import javax.swing.ToolTipManager;
53 import jalview.api.AlignViewportI;
54 import jalview.datamodel.AlignmentAnnotation;
55 import jalview.datamodel.AlignmentI;
56 import jalview.datamodel.Annotation;
57 import jalview.datamodel.ColumnSelection;
58 import jalview.datamodel.ContactListI;
59 import jalview.datamodel.HiddenColumns;
60 import jalview.datamodel.SequenceI;
61 import jalview.gui.JalviewColourChooser.ColourChooserListener;
62 import jalview.renderer.AnnotationRenderer;
63 import jalview.renderer.AwtRenderPanelI;
64 import jalview.renderer.ContactGeometry;
65 import jalview.schemes.ResidueProperties;
66 import jalview.util.Comparison;
67 import jalview.util.MessageManager;
68 import jalview.util.Platform;
69 import jalview.viewmodel.ViewportListenerI;
70 import jalview.viewmodel.ViewportRanges;
73 * AnnotationPanel displays visible portion of annotation rows below unwrapped
79 public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
80 MouseListener, MouseWheelListener, MouseMotionListener,
81 ActionListener, AdjustmentListener, Scrollable, ViewportListenerI
85 Select, Resize, Undefined
88 String HELIX = MessageManager.getString("label.helix");
90 String SHEET = MessageManager.getString("label.sheet");
93 * For RNA secondary structure "stems" aka helices
95 String STEM = MessageManager.getString("label.rna_helix");
97 String LABEL = MessageManager.getString("label.label");
99 String REMOVE = MessageManager.getString("label.remove_annotation");
101 String COLOUR = MessageManager.getString("action.colour");
103 public final Color HELIX_COLOUR = Color.red.darker();
105 public final Color SHEET_COLOUR = Color.green.darker().darker();
107 public final Color STEM_COLOUR = Color.blue.darker();
110 public AlignViewport av;
114 public int activeRow = -1;
116 public BufferedImage image;
118 public volatile BufferedImage fadedImage;
120 // private Graphics2D gg;
122 public FontMetrics fm;
124 public int imgWidth = 0;
126 boolean fastPaint = false;
128 // Used For mouse Dragging and resizing graphs
129 int graphStretch = -1;
131 int mouseDragLastX = -1;
133 int mouseDragLastY = -1;
135 DragMode dragMode = DragMode.Undefined;
137 boolean mouseDragging = false;
139 // for editing cursor
144 public final AnnotationRenderer renderer;
146 private MouseWheelListener[] _mwl;
149 * Creates a new AnnotationPanel object.
154 public AnnotationPanel(AlignmentPanel ap)
156 ToolTipManager.sharedInstance().registerComponent(this);
157 ToolTipManager.sharedInstance().setInitialDelay(0);
158 ToolTipManager.sharedInstance().setDismissDelay(10000);
161 this.setLayout(null);
162 addMouseListener(this);
163 addMouseMotionListener(this);
164 ap.annotationScroller.getVerticalScrollBar()
165 .addAdjustmentListener(this);
166 // save any wheel listeners on the scroller, so we can propagate scroll
168 _mwl = ap.annotationScroller.getMouseWheelListeners();
169 // and then set our own listener to consume all mousewheel events
170 ap.annotationScroller.addMouseWheelListener(this);
171 renderer = new AnnotationRenderer();
173 av.getRanges().addPropertyChangeListener(this);
176 public AnnotationPanel(AlignViewport av)
179 renderer = new AnnotationRenderer();
183 public void mouseWheelMoved(MouseWheelEvent e)
188 double wheelRotation = e.getPreciseWheelRotation();
189 if (wheelRotation > 0)
191 av.getRanges().scrollRight(true);
193 else if (wheelRotation < 0)
195 av.getRanges().scrollRight(false);
200 // TODO: find the correct way to let the event bubble up to
201 // ap.annotationScroller
202 for (MouseWheelListener mwl : _mwl)
206 mwl.mouseWheelMoved(e);
217 public Dimension getPreferredScrollableViewportSize()
219 Dimension ps = getPreferredSize();
220 return new Dimension(ps.width, adjustForAlignFrame(false, ps.height));
224 public int getScrollableBlockIncrement(Rectangle visibleRect,
225 int orientation, int direction)
231 public boolean getScrollableTracksViewportHeight()
237 public boolean getScrollableTracksViewportWidth()
243 public int getScrollableUnitIncrement(Rectangle visibleRect,
244 int orientation, int direction)
253 * java.awt.event.AdjustmentListener#adjustmentValueChanged(java.awt.event
257 public void adjustmentValueChanged(AdjustmentEvent evt)
259 // update annotation label display
260 ap.getAlabels().setScrollOffset(-evt.getValue());
264 * Calculates the height of the annotation displayed in the annotation panel.
265 * Callers should normally call the ap.adjustAnnotationHeight method to ensure
266 * all annotation associated components are updated correctly.
269 public int adjustPanelHeight()
271 int height = av.calcPanelHeight();
272 this.setPreferredSize(new Dimension(1, height));
275 // revalidate only when the alignment panel is fully constructed
289 public void actionPerformed(ActionEvent evt)
291 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
296 Annotation[] anot = aa[activeRow].annotations;
298 if (anot.length < av.getColumnSelection().getMax())
300 Annotation[] temp = new Annotation[av.getColumnSelection().getMax()
302 System.arraycopy(anot, 0, temp, 0, anot.length);
304 aa[activeRow].annotations = anot;
307 String action = evt.getActionCommand();
308 if (action.equals(REMOVE))
310 for (int index : av.getColumnSelection().getSelected())
312 if (av.getAlignment().getHiddenColumns().isVisible(index))
318 else if (action.equals(LABEL))
320 String exMesg = collectAnnotVals(anot, LABEL);
321 String label = JvOptionPane.showInputDialog(
322 MessageManager.getString("label.enter_label"), exMesg);
329 if ((label.length() > 0) && !aa[activeRow].hasText)
331 aa[activeRow].hasText = true;
334 for (int index : av.getColumnSelection().getSelected())
336 if (!av.getAlignment().getHiddenColumns().isVisible(index))
341 if (anot[index] == null)
343 anot[index] = new Annotation(label, "", ' ', 0);
347 anot[index].displayCharacter = label;
351 else if (action.equals(COLOUR))
353 final Annotation[] fAnot = anot;
354 String title = MessageManager
355 .getString("label.select_foreground_colour");
356 ColourChooserListener listener = new ColourChooserListener()
359 public void colourSelected(Color c)
361 HiddenColumns hiddenColumns = av.getAlignment()
363 for (int index : av.getColumnSelection().getSelected())
365 if (hiddenColumns.isVisible(index))
367 if (fAnot[index] == null)
369 fAnot[index] = new Annotation("", "", ' ', 0);
371 fAnot[index].colour = c;
376 JalviewColourChooser.showColourChooser(this, title, Color.black,
380 // HELIX, SHEET or STEM
383 String symbol = "\u03B1"; // alpha
385 if (action.equals(HELIX))
389 else if (action.equals(SHEET))
392 symbol = "\u03B2"; // beta
395 // Added by LML to color stems
396 else if (action.equals(STEM))
399 int column = av.getColumnSelection().getSelectedRanges().get(0)[0];
400 symbol = aa[activeRow].getDefaultRnaHelixSymbol(column);
403 if (!aa[activeRow].hasIcons)
405 aa[activeRow].hasIcons = true;
408 String label = JvOptionPane.showInputDialog(MessageManager
409 .getString("label.enter_label_for_the_structure"), symbol);
416 if ((label.length() > 0) && !aa[activeRow].hasText)
418 aa[activeRow].hasText = true;
419 if (action.equals(STEM))
421 aa[activeRow].showAllColLabels = true;
424 for (int index : av.getColumnSelection().getSelected())
426 if (!av.getAlignment().getHiddenColumns().isVisible(index))
431 if (anot[index] == null)
433 anot[index] = new Annotation(label, "", type, 0);
436 anot[index].secondaryStructure = type != 'S' ? type
437 : label.length() == 0 ? ' ' : label.charAt(0);
438 anot[index].displayCharacter = label;
443 av.getAlignment().validateAnnotation(aa[activeRow]);
444 ap.alignmentChanged();
445 ap.alignFrame.setMenusForViewport();
453 * Returns any existing annotation concatenated as a string. For each
454 * annotation, takes the description, if any, else the secondary structure
455 * character (if type is HELIX, SHEET or STEM), else the display character (if
462 private String collectAnnotVals(Annotation[] anots, String type)
464 // TODO is this method wanted? why? 'last' is not used
466 StringBuilder collatedInput = new StringBuilder(64);
468 ColumnSelection viscols = av.getColumnSelection();
469 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
472 * the selection list (read-only view) is in selection order, not
473 * column order; make a copy so we can sort it
475 List<Integer> selected = new ArrayList<>(viscols.getSelected());
476 Collections.sort(selected);
477 for (int index : selected)
479 // always check for current display state - just in case
480 if (!hidden.isVisible(index))
484 String tlabel = null;
485 if (anots[index] != null)
486 { // LML added stem code
487 if (type.equals(HELIX) || type.equals(SHEET) || type.equals(STEM)
488 || type.equals(LABEL))
490 tlabel = anots[index].description;
491 if (tlabel == null || tlabel.length() < 1)
493 if (type.equals(HELIX) || type.equals(SHEET)
494 || type.equals(STEM))
496 tlabel = "" + anots[index].secondaryStructure;
500 tlabel = "" + anots[index].displayCharacter;
504 if (tlabel != null && !tlabel.equals(last))
506 if (last.length() > 0)
508 collatedInput.append(" ");
510 collatedInput.append(tlabel);
514 return collatedInput.toString();
518 * Action on right mouse pressed on Mac is to show a pop-up menu for the
519 * annotation. Action on left mouse pressed is to find which annotation is
520 * pressed and mark the start of a column selection or graph resize operation.
525 public void mousePressed(MouseEvent evt)
528 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
533 mouseDragLastX = evt.getX();
534 mouseDragLastY = evt.getY();
537 * add visible annotation heights until we reach the y
538 * position, to find which annotation it is in
543 final int y = evt.getY();
544 for (int i = 0; i < aa.length; i++)
548 height += aa[i].height;
557 else if (aa[i].graph != 0)
560 * we have clicked on a resizable graph annotation
569 * isPopupTrigger fires in mousePressed on Mac,
570 * not until mouseRelease on Windows
572 if (evt.isPopupTrigger() && activeRow != -1)
574 showPopupMenu(y, evt.getX());
578 ap.getScalePanel().mousePressed(evt);
582 * Construct and display a context menu at the right-click position
587 void showPopupMenu(final int y, int x)
589 if (av.getColumnSelection() == null
590 || av.getColumnSelection().isEmpty())
595 JPopupMenu pop = new JPopupMenu(
596 MessageManager.getString("label.structure_type"));
599 * Just display the needed structure options
601 if (av.getAlignment().isNucleotide())
603 item = new JMenuItem(STEM);
604 item.addActionListener(this);
609 item = new JMenuItem(HELIX);
610 item.addActionListener(this);
612 item = new JMenuItem(SHEET);
613 item.addActionListener(this);
616 item = new JMenuItem(LABEL);
617 item.addActionListener(this);
619 item = new JMenuItem(COLOUR);
620 item.addActionListener(this);
622 item = new JMenuItem(REMOVE);
623 item.addActionListener(this);
625 pop.show(this, x, y);
629 * Action on mouse up is to clear mouse drag data and call mouseReleased on
630 * ScalePanel, to deal with defining the selection group (if any) defined by
636 public void mouseReleased(MouseEvent evt)
641 mouseDragging = false;
642 if (dragMode == DragMode.Resize)
644 ap.adjustAnnotationHeight();
646 dragMode = DragMode.Undefined;
647 ap.getScalePanel().mouseReleased(evt);
650 * isPopupTrigger is set in mouseReleased on Windows
651 * (in mousePressed on Mac)
653 if (evt.isPopupTrigger() && activeRow != -1)
655 showPopupMenu(evt.getY(), evt.getX());
667 public void mouseEntered(MouseEvent evt)
669 this.mouseDragging = false;
670 ap.getScalePanel().mouseEntered(evt);
674 * On leaving the panel, calls ScalePanel.mouseExited to deal with scrolling
675 * with column selection on a mouse drag
680 public void mouseExited(MouseEvent evt)
682 ap.getScalePanel().mouseExited(evt);
686 * Action on starting or continuing a mouse drag. There are two possible
689 * <li>drag up or down on a graphed annotation increases or decreases the
690 * height of the graph</li>
691 * <li>dragging left or right selects the columns dragged across</li>
693 * A drag on a graph annotation is treated as column selection if it starts
694 * with more horizontal than vertical movement, and as resize if it starts
695 * with more vertical than horizontal movement. Once started, the drag does
701 public void mouseDragged(MouseEvent evt)
704 * if dragMode is Undefined:
705 * - set to Select if dx > dy
706 * - set to Resize if dy > dx
707 * - do nothing if dx == dy
709 final int x = evt.getX();
710 final int y = evt.getY();
711 if (dragMode == DragMode.Undefined)
713 int dx = Math.abs(x - mouseDragLastX);
714 int dy = Math.abs(y - mouseDragLastY);
715 if (graphStretch == -1 || dx > dy)
718 * mostly horizontal drag, or not a graph annotation
720 dragMode = DragMode.Select;
725 * mostly vertical drag
727 dragMode = DragMode.Resize;
731 if (dragMode == DragMode.Undefined)
734 * drag is diagonal - defer deciding whether to
735 * treat as up/down or left/right
742 if (dragMode == DragMode.Resize)
745 * resize graph annotation if mouse was dragged up or down
747 int deltaY = mouseDragLastY - evt.getY();
750 AlignmentAnnotation graphAnnotation = av.getAlignment()
751 .getAlignmentAnnotation()[graphStretch];
752 int newHeight = Math.max(0, graphAnnotation.graphHeight + deltaY);
753 graphAnnotation.graphHeight = newHeight;
755 ap.paintAlignment(false, false);
761 * for mouse drag left or right, delegate to
762 * ScalePanel to adjust the column selection
764 ap.getScalePanel().mouseDragged(evt);
774 * Constructs the tooltip, and constructs and displays a status message, for
775 * the current mouse position
780 public void mouseMoved(MouseEvent evt)
782 int yPos = evt.getY();
783 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
784 int rowAndOffset[] = getRowIndexAndOffset(yPos, aa);
785 int row = rowAndOffset[0];
789 this.setToolTipText(null);
793 int column = (evt.getX() / av.getCharWidth())
794 + av.getRanges().getStartRes();
795 column = Math.min(column, av.getRanges().getEndRes());
797 if (av.hasHiddenColumns())
799 column = av.getAlignment().getHiddenColumns()
800 .visibleToAbsoluteColumn(column);
803 AlignmentAnnotation ann = aa[row];
804 if (row > -1 && ann.annotations != null
805 && column < ann.annotations.length)
807 String toolTip = buildToolTip(ann, column, aa, rowAndOffset[1], av);
808 setToolTipText(toolTip == null ? null
809 : JvSwingUtils.wrapTooltip(true, toolTip));
810 String msg = getStatusMessage(av.getAlignment(), column, ann,
811 rowAndOffset[1], av);
812 ap.alignFrame.setStatus(msg);
816 this.setToolTipText(null);
817 ap.alignFrame.setStatus(" ");
822 * Answers the index in the annotations array of the visible annotation at the
823 * given y position. This is done by adding the heights of visible annotations
824 * until the y position has been exceeded. Answers -1 if no annotations are
825 * visible, or the y position is below all annotations.
831 static int getRowIndex(int yPos, AlignmentAnnotation[] aa)
837 return getRowIndexAndOffset(yPos, aa)[0];
840 static int[] getRowIndexAndOffset(int yPos, AlignmentAnnotation[] aa)
842 int[] res = new int[2];
850 int height = 0, lheight = 0;
852 for (int i = 0; i < aa.length; i++)
857 height += aa[i].height;
864 res[1] = yPos - lheight;
872 * Answers a tooltip for the annotation at the current mouse position, not
873 * wrapped in <html> tags (apply if wanted). Answers null if there is no
879 * @param rowAndOffset
881 static String buildToolTip(AlignmentAnnotation ann, int column,
882 AlignmentAnnotation[] anns, int rowAndOffset, AlignViewportI av)
884 String tooltip = null;
885 if (ann.graphGroup > -1)
887 StringBuilder tip = new StringBuilder(32);
888 boolean first = true;
889 for (int i = 0; i < anns.length; i++)
891 if (anns[i].graphGroup == ann.graphGroup
892 && anns[i].annotations[column] != null)
899 tip.append(anns[i].label);
900 String description = anns[i].annotations[column].description;
901 if (description != null && description.length() > 0)
903 tip.append(" ").append(description);
907 tooltip = first ? null : tip.toString();
909 else if (column < ann.annotations.length
910 && ann.annotations[column] != null)
912 tooltip = ann.annotations[column].description;
914 // TODO abstract tooltip generator so different implementations can be built
915 if (ann.graph == AlignmentAnnotation.CUSTOMRENDERER)
917 ContactListI clist = av.getContactList(ann, column);
920 ContactGeometry cgeom = new ContactGeometry(clist, ann.graphHeight);
921 ContactGeometry.contactInterval ci = cgeom.mapFor(rowAndOffset,
923 tooltip += "Contact from " + ci.cStart + " to " + ci.cEnd;
930 * Constructs and returns the status bar message
935 * @param rowAndOffset
937 static String getStatusMessage(AlignmentI al, int column,
938 AlignmentAnnotation ann, int rowAndOffset, AlignViewportI av)
941 * show alignment column and annotation description if any
943 StringBuilder text = new StringBuilder(32);
944 text.append(MessageManager.getString("label.column")).append(" ")
947 if (column < ann.annotations.length && ann.annotations[column] != null)
949 String description = ann.annotations[column].description;
950 if (description != null && description.trim().length() > 0)
952 text.append(" ").append(description);
957 * if the annotation is sequence-specific, show the sequence number
958 * in the alignment, and (if not a gap) the residue and position
960 SequenceI seqref = ann.sequenceRef;
963 int seqIndex = al.findIndex(seqref);
966 text.append(", ").append(MessageManager.getString("label.sequence"))
967 .append(" ").append(seqIndex + 1);
968 char residue = seqref.getCharAt(column);
969 if (!Comparison.isGap(residue))
973 if (al.isNucleotide())
975 name = ResidueProperties.nucleotideName
976 .get(String.valueOf(residue));
977 text.append(" Nucleotide: ")
978 .append(name != null ? name : residue);
982 name = 'X' == residue ? "X"
983 : ('*' == residue ? "STOP"
984 : ResidueProperties.aa2Triplet
985 .get(String.valueOf(residue)));
986 text.append(" Residue: ").append(name != null ? name : residue);
988 int residuePos = seqref.findPosition(column);
989 text.append(" (").append(residuePos).append(")");
994 return text.toString();
1004 public void mouseClicked(MouseEvent evt)
1006 // if (activeRow != -1)
1008 // AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
1009 // AlignmentAnnotation anot = aa[activeRow];
1013 // TODO mouseClicked-content and drawCursor are quite experimental!
1014 public void drawCursor(Graphics graphics, SequenceI seq, int res, int x1,
1017 int pady = av.getCharHeight() / 5;
1019 graphics.setColor(Color.black);
1020 graphics.fillRect(x1, y1, av.getCharWidth(), av.getCharHeight());
1022 if (av.validCharWidth)
1024 graphics.setColor(Color.white);
1026 char s = seq.getCharAt(res);
1028 charOffset = (av.getCharWidth() - fm.charWidth(s)) / 2;
1029 graphics.drawString(String.valueOf(s), charOffset + x1,
1030 (y1 + av.getCharHeight()) - pady);
1035 private volatile boolean imageFresh = false;
1037 private Rectangle visibleRect = new Rectangle(),
1038 clipBounds = new Rectangle();
1047 public void paintComponent(Graphics g)
1050 // BH: note that this method is generally recommended to
1051 // call super.paintComponent(g). Otherwise, the children of this
1052 // component will not be rendered. That is not needed here
1053 // because AnnotationPanel does not have any children. It is
1054 // just a JPanel contained in a JViewPort.
1056 computeVisibleRect(visibleRect);
1058 g.setColor(Color.white);
1059 g.fillRect(0, 0, visibleRect.width, visibleRect.height);
1063 // BH 2018 optimizing generation of new Rectangle().
1065 || (visibleRect.width != (clipBounds = g
1066 .getClipBounds(clipBounds)).width)
1067 || (visibleRect.height != clipBounds.height))
1070 g.drawImage(image, 0, 0, this);
1075 imgWidth = (av.getRanges().getEndRes() - av.getRanges().getStartRes()
1076 + 1) * av.getCharWidth();
1082 if (image == null || imgWidth != image.getWidth(this)
1083 || image.getHeight(this) != getHeight())
1087 image = new BufferedImage(imgWidth,
1088 ap.getAnnotationPanel().getHeight(),
1089 BufferedImage.TYPE_INT_RGB);
1090 } catch (OutOfMemoryError oom)
1095 } catch (Exception x)
1100 "Couldn't allocate memory to redraw screen. Please restart Jalview",
1104 gg = (Graphics2D) image.getGraphics();
1108 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
1109 RenderingHints.VALUE_ANTIALIAS_ON);
1112 gg.setFont(av.getFont());
1113 fm = gg.getFontMetrics();
1114 gg.setColor(Color.white);
1115 gg.fillRect(0, 0, imgWidth, image.getHeight());
1120 gg = (Graphics2D) image.getGraphics();
1124 drawComponent(gg, av.getRanges().getStartRes(),
1125 av.getRanges().getEndRes() + 1);
1128 g.drawImage(image, 0, 0, this);
1132 * set true to enable redraw timing debug output on stderr
1134 private final boolean debugRedraw = false;
1137 * non-Thread safe repaint
1140 * repaint with horizontal shift in alignment
1142 public void fastPaint(int horizontal)
1144 if ((horizontal == 0) || image == null
1145 || av.getAlignment().getAlignmentAnnotation() == null
1146 || av.getAlignment().getAlignmentAnnotation().length < 1
1147 || av.isCalcInProgress())
1153 int sr = av.getRanges().getStartRes();
1154 int er = av.getRanges().getEndRes() + 1;
1157 Graphics2D gg = (Graphics2D) image.getGraphics();
1159 if (imgWidth > Math.abs(horizontal * av.getCharWidth()))
1161 // scroll is less than imgWidth away so can re-use buffered graphics
1162 gg.copyArea(0, 0, imgWidth, getHeight(),
1163 -horizontal * av.getCharWidth(), 0);
1165 if (horizontal > 0) // scrollbar pulled right, image to the left
1167 transX = (er - sr - horizontal) * av.getCharWidth();
1168 sr = er - horizontal;
1170 else if (horizontal < 0)
1172 er = sr - horizontal;
1175 gg.translate(transX, 0);
1177 drawComponent(gg, sr, er);
1179 gg.translate(-transX, 0);
1185 // Call repaint on alignment panel so that repaints from other alignment
1186 // panel components can be aggregated. Otherwise performance of the overview
1187 // window and others may be adversely affected.
1188 av.getAlignPanel().repaint();
1191 private volatile boolean lastImageGood = false;
1203 public void drawComponent(Graphics g, int startRes, int endRes)
1205 BufferedImage oldFaded = fadedImage;
1206 if (av.isCalcInProgress())
1210 lastImageGood = false;
1213 // We'll keep a record of the old image,
1214 // and draw a faded image until the calculation
1217 && (fadedImage == null || fadedImage.getWidth() != imgWidth
1218 || fadedImage.getHeight() != image.getHeight()))
1220 // System.err.println("redraw faded image ("+(fadedImage==null ?
1221 // "null image" : "") + " lastGood="+lastImageGood+")");
1222 fadedImage = new BufferedImage(imgWidth, image.getHeight(),
1223 BufferedImage.TYPE_INT_RGB);
1225 Graphics2D fadedG = (Graphics2D) fadedImage.getGraphics();
1227 fadedG.setColor(Color.white);
1228 fadedG.fillRect(0, 0, imgWidth, image.getHeight());
1230 fadedG.setComposite(
1231 AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .3f));
1232 fadedG.drawImage(image, 0, 0, this);
1235 // make sure we don't overwrite the last good faded image until all
1236 // calculations have finished
1237 lastImageGood = false;
1242 if (fadedImage != null)
1244 oldFaded = fadedImage;
1249 g.setColor(Color.white);
1250 g.fillRect(0, 0, (endRes - startRes) * av.getCharWidth(), getHeight());
1252 g.setFont(av.getFont());
1255 fm = g.getFontMetrics();
1258 if ((av.getAlignment().getAlignmentAnnotation() == null)
1259 || (av.getAlignment().getAlignmentAnnotation().length < 1))
1261 g.setColor(Color.white);
1262 g.fillRect(0, 0, getWidth(), getHeight());
1263 g.setColor(Color.black);
1264 if (av.validCharWidth)
1266 g.drawString(MessageManager
1267 .getString("label.alignment_has_no_annotations"), 20, 15);
1272 lastImageGood = renderer.drawComponent(this, av, g, activeRow, startRes,
1274 if (!lastImageGood && fadedImage == null)
1276 fadedImage = oldFaded;
1281 public FontMetrics getFontMetrics()
1287 public Image getFadedImage()
1293 public int getFadedImageWidth()
1298 private int[] bounds = new int[2];
1301 public int[] getVisibleVRange()
1303 if (ap != null && ap.getAlabels() != null)
1305 int sOffset = -ap.getAlabels().getScrollOffset();
1306 int visHeight = sOffset + ap.annotationSpaceFillerHolder.getHeight();
1307 bounds[0] = sOffset;
1308 bounds[1] = visHeight;
1318 * Try to ensure any references held are nulled
1320 public void dispose()
1330 * I created the renderer so I will dispose of it
1332 if (renderer != null)
1339 public void propertyChange(PropertyChangeEvent evt)
1341 // Respond to viewport range changes (e.g. alignment panel was scrolled)
1342 // Both scrolling and resizing change viewport ranges: scrolling changes
1343 // both start and end points, but resize only changes end values.
1344 // Here we only want to fastpaint on a scroll, with resize using a normal
1345 // paint, so scroll events are identified as changes to the horizontal or
1346 // vertical start value.
1347 if (evt.getPropertyName().equals(ViewportRanges.STARTRES))
1349 fastPaint((int) evt.getNewValue() - (int) evt.getOldValue());
1351 else if (evt.getPropertyName().equals(ViewportRanges.STARTRESANDSEQ))
1353 fastPaint(((int[]) evt.getNewValue())[0]
1354 - ((int[]) evt.getOldValue())[0]);
1356 else if (evt.getPropertyName().equals(ViewportRanges.MOVE_VIEWPORT))
1363 * computes the visible height of the annotation panel
1365 * @param adjustPanelHeight
1366 * - when false, just adjust existing height according to other
1368 * @param annotationHeight
1369 * @return height to use for the ScrollerPreferredVisibleSize
1371 public int adjustForAlignFrame(boolean adjustPanelHeight,
1372 int annotationHeight)
1375 * Estimate available height in the AlignFrame for alignment +
1376 * annotations. Deduct an estimate for title bar, menu bar, scale panel,
1377 * hscroll, status bar, insets.
1379 int stuff = (ap.getViewName() != null ? 30 : 0)
1380 + (Platform.isAMacAndNotJS() ? 120 : 140);
1381 int availableHeight = ap.alignFrame.getHeight() - stuff;
1382 int rowHeight = av.getCharHeight();
1384 if (adjustPanelHeight)
1386 int alignmentHeight = rowHeight * av.getAlignment().getHeight();
1389 * If not enough vertical space, maximize annotation height while keeping
1390 * at least two rows of alignment visible
1392 if (annotationHeight + alignmentHeight > availableHeight)
1394 annotationHeight = Math.min(annotationHeight,
1395 availableHeight - 2 * rowHeight);
1400 // maintain same window layout whilst updating sliders
1401 annotationHeight = Math.min(ap.annotationScroller.getSize().height,
1402 availableHeight - 2 * rowHeight);
1404 return annotationHeight;