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, MatrixSelect
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;
139 DragMode dragMode = DragMode.Undefined;
141 boolean mouseDragging = false;
143 // for editing cursor
148 public final AnnotationRenderer renderer;
150 private MouseWheelListener[] _mwl;
153 * Creates a new AnnotationPanel object.
158 public AnnotationPanel(AlignmentPanel ap)
160 ToolTipManager.sharedInstance().registerComponent(this);
161 ToolTipManager.sharedInstance().setInitialDelay(0);
162 ToolTipManager.sharedInstance().setDismissDelay(10000);
165 this.setLayout(null);
166 addMouseListener(this);
167 addMouseMotionListener(this);
168 ap.annotationScroller.getVerticalScrollBar()
169 .addAdjustmentListener(this);
170 // save any wheel listeners on the scroller, so we can propagate scroll
172 _mwl = ap.annotationScroller.getMouseWheelListeners();
173 // and then set our own listener to consume all mousewheel events
174 ap.annotationScroller.addMouseWheelListener(this);
175 renderer = new AnnotationRenderer();
177 av.getRanges().addPropertyChangeListener(this);
180 public AnnotationPanel(AlignViewport av)
183 renderer = new AnnotationRenderer();
187 public void mouseWheelMoved(MouseWheelEvent e)
192 double wheelRotation = e.getPreciseWheelRotation();
193 if (wheelRotation > 0)
195 av.getRanges().scrollRight(true);
197 else if (wheelRotation < 0)
199 av.getRanges().scrollRight(false);
204 // TODO: find the correct way to let the event bubble up to
205 // ap.annotationScroller
206 for (MouseWheelListener mwl : _mwl)
210 mwl.mouseWheelMoved(e);
221 public Dimension getPreferredScrollableViewportSize()
223 Dimension ps = getPreferredSize();
224 return new Dimension(ps.width, adjustForAlignFrame(false, ps.height));
228 public int getScrollableBlockIncrement(Rectangle visibleRect,
229 int orientation, int direction)
235 public boolean getScrollableTracksViewportHeight()
241 public boolean getScrollableTracksViewportWidth()
247 public int getScrollableUnitIncrement(Rectangle visibleRect,
248 int orientation, int direction)
257 * java.awt.event.AdjustmentListener#adjustmentValueChanged(java.awt.event
261 public void adjustmentValueChanged(AdjustmentEvent evt)
263 // update annotation label display
264 ap.getAlabels().setScrollOffset(-evt.getValue());
268 * Calculates the height of the annotation displayed in the annotation panel.
269 * Callers should normally call the ap.adjustAnnotationHeight method to ensure
270 * all annotation associated components are updated correctly.
273 public int adjustPanelHeight()
275 int height = av.calcPanelHeight();
276 this.setPreferredSize(new Dimension(1, height));
279 // revalidate only when the alignment panel is fully constructed
293 public void actionPerformed(ActionEvent evt)
295 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
300 Annotation[] anot = aa[activeRow].annotations;
302 if (anot.length < av.getColumnSelection().getMax())
304 Annotation[] temp = new Annotation[av.getColumnSelection().getMax()
306 System.arraycopy(anot, 0, temp, 0, anot.length);
308 aa[activeRow].annotations = anot;
311 String action = evt.getActionCommand();
312 if (action.equals(REMOVE))
314 for (int index : av.getColumnSelection().getSelected())
316 if (av.getAlignment().getHiddenColumns().isVisible(index))
322 else if (action.equals(LABEL))
324 String exMesg = collectAnnotVals(anot, LABEL);
325 String label = JvOptionPane.showInputDialog(
326 MessageManager.getString("label.enter_label"), exMesg);
333 if ((label.length() > 0) && !aa[activeRow].hasText)
335 aa[activeRow].hasText = true;
338 for (int index : av.getColumnSelection().getSelected())
340 if (!av.getAlignment().getHiddenColumns().isVisible(index))
345 if (anot[index] == null)
347 anot[index] = new Annotation(label, "", ' ', 0);
351 anot[index].displayCharacter = label;
355 else if (action.equals(COLOUR))
357 final Annotation[] fAnot = anot;
358 String title = MessageManager
359 .getString("label.select_foreground_colour");
360 ColourChooserListener listener = new ColourChooserListener()
363 public void colourSelected(Color c)
365 HiddenColumns hiddenColumns = av.getAlignment()
367 for (int index : av.getColumnSelection().getSelected())
369 if (hiddenColumns.isVisible(index))
371 if (fAnot[index] == null)
373 fAnot[index] = new Annotation("", "", ' ', 0);
375 fAnot[index].colour = c;
380 JalviewColourChooser.showColourChooser(this, title, Color.black,
384 // HELIX, SHEET or STEM
387 String symbol = "\u03B1"; // alpha
389 if (action.equals(HELIX))
393 else if (action.equals(SHEET))
396 symbol = "\u03B2"; // beta
399 // Added by LML to color stems
400 else if (action.equals(STEM))
403 int column = av.getColumnSelection().getSelectedRanges().get(0)[0];
404 symbol = aa[activeRow].getDefaultRnaHelixSymbol(column);
407 if (!aa[activeRow].hasIcons)
409 aa[activeRow].hasIcons = true;
412 String label = JvOptionPane.showInputDialog(MessageManager
413 .getString("label.enter_label_for_the_structure"), symbol);
420 if ((label.length() > 0) && !aa[activeRow].hasText)
422 aa[activeRow].hasText = true;
423 if (action.equals(STEM))
425 aa[activeRow].showAllColLabels = true;
428 for (int index : av.getColumnSelection().getSelected())
430 if (!av.getAlignment().getHiddenColumns().isVisible(index))
435 if (anot[index] == null)
437 anot[index] = new Annotation(label, "", type, 0);
440 anot[index].secondaryStructure = type != 'S' ? type
441 : label.length() == 0 ? ' ' : label.charAt(0);
442 anot[index].displayCharacter = label;
447 av.getAlignment().validateAnnotation(aa[activeRow]);
448 ap.alignmentChanged();
449 ap.alignFrame.setMenusForViewport();
457 * Returns any existing annotation concatenated as a string. For each
458 * annotation, takes the description, if any, else the secondary structure
459 * character (if type is HELIX, SHEET or STEM), else the display character (if
466 private String collectAnnotVals(Annotation[] anots, String type)
468 // TODO is this method wanted? why? 'last' is not used
470 StringBuilder collatedInput = new StringBuilder(64);
472 ColumnSelection viscols = av.getColumnSelection();
473 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
476 * the selection list (read-only view) is in selection order, not
477 * column order; make a copy so we can sort it
479 List<Integer> selected = new ArrayList<>(viscols.getSelected());
480 Collections.sort(selected);
481 for (int index : selected)
483 // always check for current display state - just in case
484 if (!hidden.isVisible(index))
488 String tlabel = null;
489 if (anots[index] != null)
490 { // LML added stem code
491 if (type.equals(HELIX) || type.equals(SHEET) || type.equals(STEM)
492 || type.equals(LABEL))
494 tlabel = anots[index].description;
495 if (tlabel == null || tlabel.length() < 1)
497 if (type.equals(HELIX) || type.equals(SHEET)
498 || type.equals(STEM))
500 tlabel = "" + anots[index].secondaryStructure;
504 tlabel = "" + anots[index].displayCharacter;
508 if (tlabel != null && !tlabel.equals(last))
510 if (last.length() > 0)
512 collatedInput.append(" ");
514 collatedInput.append(tlabel);
518 return collatedInput.toString();
522 * Action on right mouse pressed on Mac is to show a pop-up menu for the
523 * annotation. Action on left mouse pressed is to find which annotation is
524 * pressed and mark the start of a column selection or graph resize operation.
529 public void mousePressed(MouseEvent evt)
532 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
537 mouseDragLastX = evt.getX();
538 mouseDragLastY = evt.getY();
541 * add visible annotation heights until we reach the y
542 * position, to find which annotation it is in
547 // todo could reuse getRowIndexAndOffset ?
548 final int y = evt.getY();
550 for (int i = 0; i < aa.length; i++)
554 height += aa[i].height;
563 else if (aa[i].graph != 0)
566 * we have clicked on a resizable graph annotation
569 yOffset = height - y;
576 * isPopupTrigger fires in mousePressed on Mac,
577 * not until mouseRelease on Windows
579 if (evt.isPopupTrigger() && activeRow != -1)
581 showPopupMenu(y, evt.getX());
585 if (graphStretch != -1)
587 if (aa[graphStretch].graph == AlignmentAnnotation.CUSTOMRENDERER)
589 int currentX = getColumnForXPos(evt.getX());
590 ContactListI forCurrentX = av.getContactList(aa[graphStretch],
592 if (forCurrentX != null)
594 ContactGeometry cXcgeom = new ContactGeometry(forCurrentX,
595 aa[graphStretch].graphHeight);
596 ContactGeometry.contactInterval cXci = cXcgeom.mapFor(yOffset,
599 fr = Math.min(cXci.cStart, cXci.cEnd);
600 to = Math.max(cXci.cStart, cXci.cEnd);
601 for (int c = fr; c <= to; c++)
603 av.getColumnSelection().addElement(c);
605 av.getColumnSelection().addElement(currentX);
611 ap.getScalePanel().mousePressed(evt);
616 * Construct and display a context menu at the right-click position
621 void showPopupMenu(final int y, int x)
623 if (av.getColumnSelection() == null
624 || av.getColumnSelection().isEmpty())
629 JPopupMenu pop = new JPopupMenu(
630 MessageManager.getString("label.structure_type"));
633 * Just display the needed structure options
635 if (av.getAlignment().isNucleotide())
637 item = new JMenuItem(STEM);
638 item.addActionListener(this);
643 item = new JMenuItem(HELIX);
644 item.addActionListener(this);
646 item = new JMenuItem(SHEET);
647 item.addActionListener(this);
650 item = new JMenuItem(LABEL);
651 item.addActionListener(this);
653 item = new JMenuItem(COLOUR);
654 item.addActionListener(this);
656 item = new JMenuItem(REMOVE);
657 item.addActionListener(this);
659 pop.show(this, x, y);
663 * Action on mouse up is to clear mouse drag data and call mouseReleased on
664 * ScalePanel, to deal with defining the selection group (if any) defined by
670 public void mouseReleased(MouseEvent evt)
672 if (dragMode == DragMode.MatrixSelect)
674 matrixSelectRange(evt);
679 mouseDragging = false;
680 if (dragMode == DragMode.Resize)
682 ap.adjustAnnotationHeight();
684 dragMode = DragMode.Undefined;
685 ap.getScalePanel().mouseReleased(evt);
688 * isPopupTrigger is set in mouseReleased on Windows
689 * (in mousePressed on Mac)
691 if (evt.isPopupTrigger() && activeRow != -1)
693 showPopupMenu(evt.getY(), evt.getX());
705 public void mouseEntered(MouseEvent evt)
707 this.mouseDragging = false;
708 ap.getScalePanel().mouseEntered(evt);
712 * On leaving the panel, calls ScalePanel.mouseExited to deal with scrolling
713 * with column selection on a mouse drag
718 public void mouseExited(MouseEvent evt)
720 ap.getScalePanel().mouseExited(evt);
724 * Action on starting or continuing a mouse drag. There are two possible
727 * <li>drag up or down on a graphed annotation increases or decreases the
728 * height of the graph</li>
729 * <li>dragging left or right selects the columns dragged across</li>
731 * A drag on a graph annotation is treated as column selection if it starts
732 * with more horizontal than vertical movement, and as resize if it starts
733 * with more vertical than horizontal movement. Once started, the drag does
739 public void mouseDragged(MouseEvent evt)
742 * if dragMode is Undefined:
743 * - set to Select if dx > dy
744 * - set to Resize if dy > dx
745 * - do nothing if dx == dy
747 final int x = evt.getX();
748 final int y = evt.getY();
749 if (dragMode == DragMode.Undefined)
751 int dx = Math.abs(x - mouseDragLastX);
752 int dy = Math.abs(y - mouseDragLastY);
753 if (graphStretch == -1 || dx > dy)
756 * mostly horizontal drag, or not a graph annotation
758 dragMode = DragMode.Select;
763 * mostly vertical drag
765 dragMode = DragMode.Resize;
768 * but could also be a matrix drag
770 if ((evt.isAltDown() || evt.isAltGraphDown()) && (av.getAlignment()
771 .getAlignmentAnnotation()[graphStretch].graph == AlignmentAnnotation.CUSTOMRENDERER))
774 * dragging in a matrix
776 dragMode = DragMode.MatrixSelect;
777 firstDragX = mouseDragLastX;
778 firstDragY = mouseDragLastY;
783 if (dragMode == DragMode.Undefined)
787 * drag is diagonal - defer deciding whether to
788 * treat as up/down or left/right
795 if (dragMode == DragMode.Resize)
798 * resize graph annotation if mouse was dragged up or down
800 int deltaY = mouseDragLastY - evt.getY();
803 AlignmentAnnotation graphAnnotation = av.getAlignment()
804 .getAlignmentAnnotation()[graphStretch];
805 int newHeight = Math.max(0, graphAnnotation.graphHeight + deltaY);
806 graphAnnotation.graphHeight = newHeight;
808 ap.paintAlignment(false, false);
811 else if (dragMode == DragMode.MatrixSelect)
814 * TODO draw a rubber band for range
820 * for mouse drag left or right, delegate to
821 * ScalePanel to adjust the column selection
823 ap.getScalePanel().mouseDragged(evt);
832 public void matrixSelectRange(MouseEvent evt)
835 * get geometry of drag
837 int fromY = Math.min(firstDragY, evt.getY());
838 int deltaY = Math.abs(firstDragY - evt.getY());
839 int toY = Math.max(firstDragY, evt.getY());
841 int[] rowIndex = getRowIndexAndOffset(fromY,
842 av.getAlignment().getAlignmentAnnotation());
844 // rectangular selection on matrix style annotation
845 AlignmentAnnotation cma = av.getAlignment()
846 .getAlignmentAnnotation()[rowIndex[0]];
848 int fromXp = Math.min(firstDragX, evt.getX());
849 int toXp = Math.max(firstDragX, evt.getX());
850 int lastX = getColumnForXPos(fromXp);
851 int currentX = getColumnForXPos(toXp);
852 ContactListI forLastX = av.getContactList(cma, lastX);
853 ContactListI forCurrentX = av.getContactList(cma, currentX);
854 if (forLastX != null && forCurrentX != null)
856 ContactGeometry lastXcgeom = new ContactGeometry(forLastX,
858 ContactGeometry.contactInterval lastXci = lastXcgeom.mapFor(
860 rowIndex[1] + ((fromY == firstDragY) ? -deltaY : deltaY));
861 ContactGeometry cXcgeom = new ContactGeometry(forCurrentX,
863 ContactGeometry.contactInterval cXci = cXcgeom.mapFor(rowIndex[1],
864 rowIndex[1] + deltaY);
865 // mark rectangular region formed by drag
866 System.err.println("Matrix Selection from last(" + lastXci.cStart
867 + "," + lastXci.cEnd + ") to cur(" + cXci.cStart + ","
870 fr = Math.min(lastXci.cStart, lastXci.cEnd);
871 to = Math.max(lastXci.cStart, lastXci.cEnd);
872 System.err.println("Marking " + fr + " to " + to);
873 for (int c = fr; c <= to; c++)
875 av.getColumnSelection().addElement(c);
877 fr = Math.min(cXci.cStart, cXci.cEnd);
878 to = Math.max(cXci.cStart, cXci.cEnd);
879 System.err.println("Marking " + fr + " to " + to);
880 for (int c = fr; c <= to; c++)
882 av.getColumnSelection().addElement(c);
884 fr = Math.min(lastX, currentX);
885 to = Math.max(lastX, currentX);
887 System.err.println("Marking " + fr + " to " + to);
888 for (int c = fr; c <= to; c++)
890 av.getColumnSelection().addElement(c);
897 * Constructs the tooltip, and constructs and displays a status message, for
898 * the current mouse position
903 public void mouseMoved(MouseEvent evt)
905 int yPos = evt.getY();
906 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
907 int rowAndOffset[] = getRowIndexAndOffset(yPos, aa);
908 int row = rowAndOffset[0];
912 this.setToolTipText(null);
916 int column = getColumnForXPos(evt.getX());
918 AlignmentAnnotation ann = aa[row];
919 if (row > -1 && ann.annotations != null
920 && column < ann.annotations.length)
922 String toolTip = buildToolTip(ann, column, aa, rowAndOffset[1], av,
924 setToolTipText(toolTip == null ? null
925 : JvSwingUtils.wrapTooltip(true, toolTip));
926 String msg = getStatusMessage(av.getAlignment(), column, ann,
927 rowAndOffset[1], av);
928 ap.alignFrame.setStatus(msg);
932 this.setToolTipText(null);
933 ap.alignFrame.setStatus(" ");
937 private int getColumnForXPos(int x)
939 int column = (x / av.getCharWidth()) + av.getRanges().getStartRes();
940 column = Math.min(column, av.getRanges().getEndRes());
942 if (av.hasHiddenColumns())
944 column = av.getAlignment().getHiddenColumns()
945 .visibleToAbsoluteColumn(column);
951 * Answers the index in the annotations array of the visible annotation at the
952 * given y position. This is done by adding the heights of visible annotations
953 * until the y position has been exceeded. Answers -1 if no annotations are
954 * visible, or the y position is below all annotations.
960 static int getRowIndex(int yPos, AlignmentAnnotation[] aa)
966 return getRowIndexAndOffset(yPos, aa)[0];
969 static int[] getRowIndexAndOffset(int yPos, AlignmentAnnotation[] aa)
971 int[] res = new int[2];
979 int height = 0, lheight = 0;
981 for (int i = 0; i < aa.length; i++)
986 height += aa[i].height;
993 res[1] = height - yPos;
1001 * Answers a tooltip for the annotation at the current mouse position, not
1002 * wrapped in <html> tags (apply if wanted). Answers null if there is no
1008 * @param rowAndOffset
1010 static String buildToolTip(AlignmentAnnotation ann, int column,
1011 AlignmentAnnotation[] anns, int rowAndOffset, AlignViewportI av,
1014 String tooltip = null;
1015 if (ann.graphGroup > -1)
1017 StringBuilder tip = new StringBuilder(32);
1018 boolean first = true;
1019 for (int i = 0; i < anns.length; i++)
1021 if (anns[i].graphGroup == ann.graphGroup
1022 && anns[i].annotations[column] != null)
1029 tip.append(anns[i].label);
1030 String description = anns[i].annotations[column].description;
1031 if (description != null && description.length() > 0)
1033 tip.append(" ").append(description);
1037 tooltip = first ? null : tip.toString();
1039 else if (column < ann.annotations.length
1040 && ann.annotations[column] != null)
1042 tooltip = ann.annotations[column].description;
1044 // TODO abstract tooltip generator so different implementations can be built
1045 if (ann.graph == AlignmentAnnotation.CUSTOMRENDERER)
1047 ContactListI clist = av.getContactList(ann, column);
1050 ContactGeometry cgeom = new ContactGeometry(clist, ann.graphHeight);
1051 ContactGeometry.contactInterval ci = cgeom.mapFor(rowAndOffset,
1053 tooltip += "Contact from " + ci.cStart + " to " + ci.cEnd;
1055 // ap.getStructureSelectionManager().mouseOverSequence(ann.sequenceRef,
1056 // new int[] {column, ci.cStart,ci.cEnd}, -1, null)
1063 * Constructs and returns the status bar message
1068 * @param rowAndOffset
1070 static String getStatusMessage(AlignmentI al, int column,
1071 AlignmentAnnotation ann, int rowAndOffset, AlignViewportI av)
1074 * show alignment column and annotation description if any
1076 StringBuilder text = new StringBuilder(32);
1077 text.append(MessageManager.getString("label.column")).append(" ")
1078 .append(column + 1);
1080 if (column < ann.annotations.length && ann.annotations[column] != null)
1082 String description = ann.annotations[column].description;
1083 if (description != null && description.trim().length() > 0)
1085 text.append(" ").append(description);
1090 * if the annotation is sequence-specific, show the sequence number
1091 * in the alignment, and (if not a gap) the residue and position
1093 SequenceI seqref = ann.sequenceRef;
1096 int seqIndex = al.findIndex(seqref);
1099 text.append(", ").append(MessageManager.getString("label.sequence"))
1100 .append(" ").append(seqIndex + 1);
1101 char residue = seqref.getCharAt(column);
1102 if (!Comparison.isGap(residue))
1106 if (al.isNucleotide())
1108 name = ResidueProperties.nucleotideName
1109 .get(String.valueOf(residue));
1110 text.append(" Nucleotide: ")
1111 .append(name != null ? name : residue);
1115 name = 'X' == residue ? "X"
1116 : ('*' == residue ? "STOP"
1117 : ResidueProperties.aa2Triplet
1118 .get(String.valueOf(residue)));
1119 text.append(" Residue: ").append(name != null ? name : residue);
1121 int residuePos = seqref.findPosition(column);
1122 text.append(" (").append(residuePos).append(")");
1127 return text.toString();
1137 public void mouseClicked(MouseEvent evt)
1139 // if (activeRow != -1)
1141 // AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
1142 // AlignmentAnnotation anot = aa[activeRow];
1146 // TODO mouseClicked-content and drawCursor are quite experimental!
1147 public void drawCursor(Graphics graphics, SequenceI seq, int res, int x1,
1150 int pady = av.getCharHeight() / 5;
1152 graphics.setColor(Color.black);
1153 graphics.fillRect(x1, y1, av.getCharWidth(), av.getCharHeight());
1155 if (av.validCharWidth)
1157 graphics.setColor(Color.white);
1159 char s = seq.getCharAt(res);
1161 charOffset = (av.getCharWidth() - fm.charWidth(s)) / 2;
1162 graphics.drawString(String.valueOf(s), charOffset + x1,
1163 (y1 + av.getCharHeight()) - pady);
1168 private volatile boolean imageFresh = false;
1170 private Rectangle visibleRect = new Rectangle(),
1171 clipBounds = new Rectangle();
1180 public void paintComponent(Graphics g)
1183 // BH: note that this method is generally recommended to
1184 // call super.paintComponent(g). Otherwise, the children of this
1185 // component will not be rendered. That is not needed here
1186 // because AnnotationPanel does not have any children. It is
1187 // just a JPanel contained in a JViewPort.
1189 computeVisibleRect(visibleRect);
1191 g.setColor(Color.white);
1192 g.fillRect(0, 0, visibleRect.width, visibleRect.height);
1196 // BH 2018 optimizing generation of new Rectangle().
1198 || (visibleRect.width != (clipBounds = g
1199 .getClipBounds(clipBounds)).width)
1200 || (visibleRect.height != clipBounds.height))
1203 g.drawImage(image, 0, 0, this);
1208 imgWidth = (av.getRanges().getEndRes() - av.getRanges().getStartRes()
1209 + 1) * av.getCharWidth();
1215 if (image == null || imgWidth != image.getWidth(this)
1216 || image.getHeight(this) != getHeight())
1220 image = new BufferedImage(imgWidth,
1221 ap.getAnnotationPanel().getHeight(),
1222 BufferedImage.TYPE_INT_RGB);
1223 } catch (OutOfMemoryError oom)
1228 } catch (Exception x)
1233 "Couldn't allocate memory to redraw screen. Please restart Jalview",
1237 gg = (Graphics2D) image.getGraphics();
1241 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
1242 RenderingHints.VALUE_ANTIALIAS_ON);
1245 gg.setFont(av.getFont());
1246 fm = gg.getFontMetrics();
1247 gg.setColor(Color.white);
1248 gg.fillRect(0, 0, imgWidth, image.getHeight());
1253 gg = (Graphics2D) image.getGraphics();
1257 drawComponent(gg, av.getRanges().getStartRes(),
1258 av.getRanges().getEndRes() + 1);
1261 g.drawImage(image, 0, 0, this);
1265 * set true to enable redraw timing debug output on stderr
1267 private final boolean debugRedraw = false;
1270 * non-Thread safe repaint
1273 * repaint with horizontal shift in alignment
1275 public void fastPaint(int horizontal)
1277 if ((horizontal == 0) || image == null
1278 || av.getAlignment().getAlignmentAnnotation() == null
1279 || av.getAlignment().getAlignmentAnnotation().length < 1
1280 || av.isCalcInProgress())
1286 int sr = av.getRanges().getStartRes();
1287 int er = av.getRanges().getEndRes() + 1;
1290 Graphics2D gg = (Graphics2D) image.getGraphics();
1292 if (imgWidth > Math.abs(horizontal * av.getCharWidth()))
1294 // scroll is less than imgWidth away so can re-use buffered graphics
1295 gg.copyArea(0, 0, imgWidth, getHeight(),
1296 -horizontal * av.getCharWidth(), 0);
1298 if (horizontal > 0) // scrollbar pulled right, image to the left
1300 transX = (er - sr - horizontal) * av.getCharWidth();
1301 sr = er - horizontal;
1303 else if (horizontal < 0)
1305 er = sr - horizontal;
1308 gg.translate(transX, 0);
1310 drawComponent(gg, sr, er);
1312 gg.translate(-transX, 0);
1318 // Call repaint on alignment panel so that repaints from other alignment
1319 // panel components can be aggregated. Otherwise performance of the overview
1320 // window and others may be adversely affected.
1321 av.getAlignPanel().repaint();
1324 private volatile boolean lastImageGood = false;
1336 public void drawComponent(Graphics g, int startRes, int endRes)
1338 BufferedImage oldFaded = fadedImage;
1339 if (av.isCalcInProgress())
1343 lastImageGood = false;
1346 // We'll keep a record of the old image,
1347 // and draw a faded image until the calculation
1350 && (fadedImage == null || fadedImage.getWidth() != imgWidth
1351 || fadedImage.getHeight() != image.getHeight()))
1353 // System.err.println("redraw faded image ("+(fadedImage==null ?
1354 // "null image" : "") + " lastGood="+lastImageGood+")");
1355 fadedImage = new BufferedImage(imgWidth, image.getHeight(),
1356 BufferedImage.TYPE_INT_RGB);
1358 Graphics2D fadedG = (Graphics2D) fadedImage.getGraphics();
1360 fadedG.setColor(Color.white);
1361 fadedG.fillRect(0, 0, imgWidth, image.getHeight());
1363 fadedG.setComposite(
1364 AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .3f));
1365 fadedG.drawImage(image, 0, 0, this);
1368 // make sure we don't overwrite the last good faded image until all
1369 // calculations have finished
1370 lastImageGood = false;
1375 if (fadedImage != null)
1377 oldFaded = fadedImage;
1382 g.setColor(Color.white);
1383 g.fillRect(0, 0, (endRes - startRes) * av.getCharWidth(), getHeight());
1385 g.setFont(av.getFont());
1388 fm = g.getFontMetrics();
1391 if ((av.getAlignment().getAlignmentAnnotation() == null)
1392 || (av.getAlignment().getAlignmentAnnotation().length < 1))
1394 g.setColor(Color.white);
1395 g.fillRect(0, 0, getWidth(), getHeight());
1396 g.setColor(Color.black);
1397 if (av.validCharWidth)
1399 g.drawString(MessageManager
1400 .getString("label.alignment_has_no_annotations"), 20, 15);
1405 lastImageGood = renderer.drawComponent(this, av, g, activeRow, startRes,
1407 if (!lastImageGood && fadedImage == null)
1409 fadedImage = oldFaded;
1414 public FontMetrics getFontMetrics()
1420 public Image getFadedImage()
1426 public int getFadedImageWidth()
1431 private int[] bounds = new int[2];
1434 public int[] getVisibleVRange()
1436 if (ap != null && ap.getAlabels() != null)
1438 int sOffset = -ap.getAlabels().getScrollOffset();
1439 int visHeight = sOffset + ap.annotationSpaceFillerHolder.getHeight();
1440 bounds[0] = sOffset;
1441 bounds[1] = visHeight;
1451 * Try to ensure any references held are nulled
1453 public void dispose()
1463 * I created the renderer so I will dispose of it
1465 if (renderer != null)
1472 public void propertyChange(PropertyChangeEvent evt)
1474 // Respond to viewport range changes (e.g. alignment panel was scrolled)
1475 // Both scrolling and resizing change viewport ranges: scrolling changes
1476 // both start and end points, but resize only changes end values.
1477 // Here we only want to fastpaint on a scroll, with resize using a normal
1478 // paint, so scroll events are identified as changes to the horizontal or
1479 // vertical start value.
1480 if (evt.getPropertyName().equals(ViewportRanges.STARTRES))
1482 fastPaint((int) evt.getNewValue() - (int) evt.getOldValue());
1484 else if (evt.getPropertyName().equals(ViewportRanges.STARTRESANDSEQ))
1486 fastPaint(((int[]) evt.getNewValue())[0]
1487 - ((int[]) evt.getOldValue())[0]);
1489 else if (evt.getPropertyName().equals(ViewportRanges.MOVE_VIEWPORT))
1496 * computes the visible height of the annotation panel
1498 * @param adjustPanelHeight
1499 * - when false, just adjust existing height according to other
1501 * @param annotationHeight
1502 * @return height to use for the ScrollerPreferredVisibleSize
1504 public int adjustForAlignFrame(boolean adjustPanelHeight,
1505 int annotationHeight)
1508 * Estimate available height in the AlignFrame for alignment +
1509 * annotations. Deduct an estimate for title bar, menu bar, scale panel,
1510 * hscroll, status bar, insets.
1512 int stuff = (ap.getViewName() != null ? 30 : 0)
1513 + (Platform.isAMacAndNotJS() ? 120 : 140);
1514 int availableHeight = ap.alignFrame.getHeight() - stuff;
1515 int rowHeight = av.getCharHeight();
1517 if (adjustPanelHeight)
1519 int alignmentHeight = rowHeight * av.getAlignment().getHeight();
1522 * If not enough vertical space, maximize annotation height while keeping
1523 * at least two rows of alignment visible
1525 if (annotationHeight + alignmentHeight > availableHeight)
1527 annotationHeight = Math.min(annotationHeight,
1528 availableHeight - 2 * rowHeight);
1533 // maintain same window layout whilst updating sliders
1534 annotationHeight = Math.min(ap.annotationScroller.getSize().height,
1535 availableHeight - 2 * rowHeight);
1537 return annotationHeight;