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.ContactRange;
60 import jalview.datamodel.HiddenColumns;
61 import jalview.datamodel.SequenceI;
62 import jalview.gui.JalviewColourChooser.ColourChooserListener;
63 import jalview.renderer.AnnotationRenderer;
64 import jalview.renderer.AwtRenderPanelI;
65 import jalview.renderer.ContactGeometry;
66 import jalview.schemes.ResidueProperties;
67 import jalview.util.Comparison;
68 import jalview.util.MessageManager;
69 import jalview.util.Platform;
70 import jalview.viewmodel.ViewportListenerI;
71 import jalview.viewmodel.ViewportRanges;
74 * AnnotationPanel displays visible portion of annotation rows below unwrapped
80 public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
81 MouseListener, MouseWheelListener, MouseMotionListener,
82 ActionListener, AdjustmentListener, Scrollable, ViewportListenerI
86 Select, Resize, Undefined, MatrixSelect
89 String HELIX = MessageManager.getString("label.helix");
91 String SHEET = MessageManager.getString("label.sheet");
94 * For RNA secondary structure "stems" aka helices
96 String STEM = MessageManager.getString("label.rna_helix");
98 String LABEL = MessageManager.getString("label.label");
100 String REMOVE = MessageManager.getString("label.remove_annotation");
102 String COLOUR = MessageManager.getString("action.colour");
104 public final Color HELIX_COLOUR = Color.red.darker();
106 public final Color SHEET_COLOUR = Color.green.darker().darker();
108 public final Color STEM_COLOUR = Color.blue.darker();
111 public AlignViewport av;
115 public int activeRow = -1;
117 public BufferedImage image;
119 public volatile BufferedImage fadedImage;
121 // private Graphics2D gg;
123 public FontMetrics fm;
125 public int imgWidth = 0;
127 boolean fastPaint = false;
129 // Used For mouse Dragging and resizing graphs
130 int graphStretch = -1;
132 int mouseDragLastX = -1;
134 int mouseDragLastY = -1;
140 DragMode dragMode = DragMode.Undefined;
142 boolean mouseDragging = false;
144 // for editing cursor
149 public final AnnotationRenderer renderer;
151 private MouseWheelListener[] _mwl;
154 * Creates a new AnnotationPanel object.
159 public AnnotationPanel(AlignmentPanel ap)
161 ToolTipManager.sharedInstance().registerComponent(this);
162 ToolTipManager.sharedInstance().setInitialDelay(0);
163 ToolTipManager.sharedInstance().setDismissDelay(10000);
166 this.setLayout(null);
167 addMouseListener(this);
168 addMouseMotionListener(this);
169 ap.annotationScroller.getVerticalScrollBar()
170 .addAdjustmentListener(this);
171 // save any wheel listeners on the scroller, so we can propagate scroll
173 _mwl = ap.annotationScroller.getMouseWheelListeners();
174 // and then set our own listener to consume all mousewheel events
175 ap.annotationScroller.addMouseWheelListener(this);
176 renderer = new AnnotationRenderer();
178 av.getRanges().addPropertyChangeListener(this);
181 public AnnotationPanel(AlignViewport av)
184 renderer = new AnnotationRenderer();
188 public void mouseWheelMoved(MouseWheelEvent e)
193 double wheelRotation = e.getPreciseWheelRotation();
194 if (wheelRotation > 0)
196 av.getRanges().scrollRight(true);
198 else if (wheelRotation < 0)
200 av.getRanges().scrollRight(false);
205 // TODO: find the correct way to let the event bubble up to
206 // ap.annotationScroller
207 for (MouseWheelListener mwl : _mwl)
211 mwl.mouseWheelMoved(e);
222 public Dimension getPreferredScrollableViewportSize()
224 Dimension ps = getPreferredSize();
225 return new Dimension(ps.width, adjustForAlignFrame(false, ps.height));
229 public int getScrollableBlockIncrement(Rectangle visibleRect,
230 int orientation, int direction)
236 public boolean getScrollableTracksViewportHeight()
242 public boolean getScrollableTracksViewportWidth()
248 public int getScrollableUnitIncrement(Rectangle visibleRect,
249 int orientation, int direction)
258 * java.awt.event.AdjustmentListener#adjustmentValueChanged(java.awt.event
262 public void adjustmentValueChanged(AdjustmentEvent evt)
264 // update annotation label display
265 ap.getAlabels().setScrollOffset(-evt.getValue());
269 * Calculates the height of the annotation displayed in the annotation panel.
270 * Callers should normally call the ap.adjustAnnotationHeight method to ensure
271 * all annotation associated components are updated correctly.
274 public int adjustPanelHeight()
276 int height = av.calcPanelHeight();
277 this.setPreferredSize(new Dimension(1, height));
280 // revalidate only when the alignment panel is fully constructed
294 public void actionPerformed(ActionEvent evt)
296 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
301 Annotation[] anot = aa[activeRow].annotations;
303 if (anot.length < av.getColumnSelection().getMax())
305 Annotation[] temp = new Annotation[av.getColumnSelection().getMax()
307 System.arraycopy(anot, 0, temp, 0, anot.length);
309 aa[activeRow].annotations = anot;
312 String action = evt.getActionCommand();
313 if (action.equals(REMOVE))
315 for (int index : av.getColumnSelection().getSelected())
317 if (av.getAlignment().getHiddenColumns().isVisible(index))
323 else if (action.equals(LABEL))
325 String exMesg = collectAnnotVals(anot, LABEL);
326 String label = JvOptionPane.showInputDialog(
327 MessageManager.getString("label.enter_label"), exMesg);
334 if ((label.length() > 0) && !aa[activeRow].hasText)
336 aa[activeRow].hasText = true;
339 for (int index : av.getColumnSelection().getSelected())
341 if (!av.getAlignment().getHiddenColumns().isVisible(index))
346 if (anot[index] == null)
348 anot[index] = new Annotation(label, "", ' ', 0);
352 anot[index].displayCharacter = label;
356 else if (action.equals(COLOUR))
358 final Annotation[] fAnot = anot;
359 String title = MessageManager
360 .getString("label.select_foreground_colour");
361 ColourChooserListener listener = new ColourChooserListener()
364 public void colourSelected(Color c)
366 HiddenColumns hiddenColumns = av.getAlignment()
368 for (int index : av.getColumnSelection().getSelected())
370 if (hiddenColumns.isVisible(index))
372 if (fAnot[index] == null)
374 fAnot[index] = new Annotation("", "", ' ', 0);
376 fAnot[index].colour = c;
381 JalviewColourChooser.showColourChooser(this, title, Color.black,
385 // HELIX, SHEET or STEM
388 String symbol = "\u03B1"; // alpha
390 if (action.equals(HELIX))
394 else if (action.equals(SHEET))
397 symbol = "\u03B2"; // beta
400 // Added by LML to color stems
401 else if (action.equals(STEM))
404 int column = av.getColumnSelection().getSelectedRanges().get(0)[0];
405 symbol = aa[activeRow].getDefaultRnaHelixSymbol(column);
408 if (!aa[activeRow].hasIcons)
410 aa[activeRow].hasIcons = true;
413 String label = JvOptionPane.showInputDialog(MessageManager
414 .getString("label.enter_label_for_the_structure"), symbol);
421 if ((label.length() > 0) && !aa[activeRow].hasText)
423 aa[activeRow].hasText = true;
424 if (action.equals(STEM))
426 aa[activeRow].showAllColLabels = true;
429 for (int index : av.getColumnSelection().getSelected())
431 if (!av.getAlignment().getHiddenColumns().isVisible(index))
436 if (anot[index] == null)
438 anot[index] = new Annotation(label, "", type, 0);
441 anot[index].secondaryStructure = type != 'S' ? type
442 : label.length() == 0 ? ' ' : label.charAt(0);
443 anot[index].displayCharacter = label;
448 av.getAlignment().validateAnnotation(aa[activeRow]);
449 ap.alignmentChanged();
450 ap.alignFrame.setMenusForViewport();
458 * Returns any existing annotation concatenated as a string. For each
459 * annotation, takes the description, if any, else the secondary structure
460 * character (if type is HELIX, SHEET or STEM), else the display character (if
467 private String collectAnnotVals(Annotation[] anots, String type)
469 // TODO is this method wanted? why? 'last' is not used
471 StringBuilder collatedInput = new StringBuilder(64);
473 ColumnSelection viscols = av.getColumnSelection();
474 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
477 * the selection list (read-only view) is in selection order, not
478 * column order; make a copy so we can sort it
480 List<Integer> selected = new ArrayList<>(viscols.getSelected());
481 Collections.sort(selected);
482 for (int index : selected)
484 // always check for current display state - just in case
485 if (!hidden.isVisible(index))
489 String tlabel = null;
490 if (anots[index] != null)
491 { // LML added stem code
492 if (type.equals(HELIX) || type.equals(SHEET) || type.equals(STEM)
493 || type.equals(LABEL))
495 tlabel = anots[index].description;
496 if (tlabel == null || tlabel.length() < 1)
498 if (type.equals(HELIX) || type.equals(SHEET)
499 || type.equals(STEM))
501 tlabel = "" + anots[index].secondaryStructure;
505 tlabel = "" + anots[index].displayCharacter;
509 if (tlabel != null && !tlabel.equals(last))
511 if (last.length() > 0)
513 collatedInput.append(" ");
515 collatedInput.append(tlabel);
519 return collatedInput.toString();
523 * Action on right mouse pressed on Mac is to show a pop-up menu for the
524 * annotation. Action on left mouse pressed is to find which annotation is
525 * pressed and mark the start of a column selection or graph resize operation.
530 public void mousePressed(MouseEvent evt)
533 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
538 mouseDragLastX = evt.getX();
539 mouseDragLastY = evt.getY();
542 * add visible annotation heights until we reach the y
543 * position, to find which annotation it is in
548 // todo could reuse getRowIndexAndOffset ?
549 final int y = evt.getY();
551 for (int i = 0; i < aa.length; i++)
555 height += aa[i].height;
564 else if (aa[i].graph != 0)
567 * we have clicked on a resizable graph annotation
570 yOffset = height - y;
577 * isPopupTrigger fires in mousePressed on Mac,
578 * not until mouseRelease on Windows
580 if (evt.isPopupTrigger() && activeRow != -1)
582 showPopupMenu(y, evt.getX());
586 if (graphStretch != -1)
589 if (aa[graphStretch].graph == AlignmentAnnotation.CUSTOMRENDERER)
591 if (evt.isAltDown() || evt.isAltGraphDown())
593 dragMode = DragMode.MatrixSelect;
594 firstDragX = mouseDragLastX;
595 firstDragY = mouseDragLastY;
599 int currentX = getColumnForXPos(evt.getX());
600 ContactListI forCurrentX = av.getContactList(aa[graphStretch],
602 if (forCurrentX != null)
604 ContactGeometry cXcgeom = new ContactGeometry(forCurrentX,
605 aa[graphStretch].graphHeight);
606 ContactGeometry.contactInterval cXci = cXcgeom.mapFor(yOffset,
609 fr = Math.min(cXci.cStart, cXci.cEnd);
610 to = Math.max(cXci.cStart, cXci.cEnd);
611 for (int c = fr; c <= to; c++)
613 av.getColumnSelection().addElement(c);
615 av.getColumnSelection().addElement(currentX);
622 ap.getScalePanel().mousePressed(evt);
627 * Construct and display a context menu at the right-click position
632 void showPopupMenu(final int y, int x)
634 if (av.getColumnSelection() == null
635 || av.getColumnSelection().isEmpty())
640 JPopupMenu pop = new JPopupMenu(
641 MessageManager.getString("label.structure_type"));
644 * Just display the needed structure options
646 if (av.getAlignment().isNucleotide())
648 item = new JMenuItem(STEM);
649 item.addActionListener(this);
654 item = new JMenuItem(HELIX);
655 item.addActionListener(this);
657 item = new JMenuItem(SHEET);
658 item.addActionListener(this);
661 item = new JMenuItem(LABEL);
662 item.addActionListener(this);
664 item = new JMenuItem(COLOUR);
665 item.addActionListener(this);
667 item = new JMenuItem(REMOVE);
668 item.addActionListener(this);
670 pop.show(this, x, y);
674 * Action on mouse up is to clear mouse drag data and call mouseReleased on
675 * ScalePanel, to deal with defining the selection group (if any) defined by
681 public void mouseReleased(MouseEvent evt)
683 if (dragMode == DragMode.MatrixSelect)
685 matrixSelectRange(evt);
692 mouseDragging = false;
693 if (dragMode == DragMode.Resize)
695 ap.adjustAnnotationHeight();
697 dragMode = DragMode.Undefined;
698 ap.getScalePanel().mouseReleased(evt);
701 * isPopupTrigger is set in mouseReleased on Windows
702 * (in mousePressed on Mac)
704 if (evt.isPopupTrigger() && activeRow != -1)
706 showPopupMenu(evt.getY(), evt.getX());
718 public void mouseEntered(MouseEvent evt)
720 this.mouseDragging = false;
721 ap.getScalePanel().mouseEntered(evt);
725 * On leaving the panel, calls ScalePanel.mouseExited to deal with scrolling
726 * with column selection on a mouse drag
731 public void mouseExited(MouseEvent evt)
733 ap.getScalePanel().mouseExited(evt);
737 * Action on starting or continuing a mouse drag. There are two possible
740 * <li>drag up or down on a graphed annotation increases or decreases the
741 * height of the graph</li>
742 * <li>dragging left or right selects the columns dragged across</li>
744 * A drag on a graph annotation is treated as column selection if it starts
745 * with more horizontal than vertical movement, and as resize if it starts
746 * with more vertical than horizontal movement. Once started, the drag does
752 public void mouseDragged(MouseEvent evt)
755 * if dragMode is Undefined:
756 * - set to Select if dx > dy
757 * - set to Resize if dy > dx
758 * - do nothing if dx == dy
760 final int x = evt.getX();
761 final int y = evt.getY();
762 if (dragMode == DragMode.Undefined)
764 int dx = Math.abs(x - mouseDragLastX);
765 int dy = Math.abs(y - mouseDragLastY);
766 if (graphStretch == -1 || dx > dy)
769 * mostly horizontal drag, or not a graph annotation
771 dragMode = DragMode.Select;
776 * mostly vertical drag
778 dragMode = DragMode.Resize;
781 * but could also be a matrix drag
783 if ((evt.isAltDown() || evt.isAltGraphDown()) && (av.getAlignment()
784 .getAlignmentAnnotation()[graphStretch].graph == AlignmentAnnotation.CUSTOMRENDERER))
787 * dragging in a matrix
789 dragMode = DragMode.MatrixSelect;
790 firstDragX = mouseDragLastX;
791 firstDragY = mouseDragLastY;
796 if (dragMode == DragMode.Undefined)
800 * drag is diagonal - defer deciding whether to
801 * treat as up/down or left/right
808 if (dragMode == DragMode.Resize)
811 * resize graph annotation if mouse was dragged up or down
813 int deltaY = mouseDragLastY - evt.getY();
816 AlignmentAnnotation graphAnnotation = av.getAlignment()
817 .getAlignmentAnnotation()[graphStretch];
818 int newHeight = Math.max(0, graphAnnotation.graphHeight + deltaY);
819 graphAnnotation.graphHeight = newHeight;
821 ap.paintAlignment(false, false);
824 else if (dragMode == DragMode.MatrixSelect)
827 * TODO draw a rubber band for range
831 ap.paintAlignment(false, false);
836 * for mouse drag left or right, delegate to
837 * ScalePanel to adjust the column selection
839 ap.getScalePanel().mouseDragged(evt);
848 public void matrixSelectRange(MouseEvent evt)
851 * get geometry of drag
853 int fromY = Math.min(firstDragY, evt.getY());
854 int toY = Math.max(firstDragY, evt.getY());
855 int fromX = Math.min(firstDragX, evt.getX());
856 int toX = Math.max(firstDragX, evt.getX());
858 int deltaY = toY - fromY;
859 int deltaX = toX - fromX;
861 int[] rowIndex = getRowIndexAndOffset(fromY,
862 av.getAlignment().getAlignmentAnnotation());
863 int[] toRowIndex = getRowIndexAndOffset(toY,
864 av.getAlignment().getAlignmentAnnotation());
866 if (rowIndex == null || toRowIndex == null)
868 System.out.println("Drag out of range. needs to be clipped");
871 if (rowIndex[0] != toRowIndex[0])
873 System.out.println("Drag went to another row. needs to be clipped");
876 // rectangular selection on matrix style annotation
877 AlignmentAnnotation cma = av.getAlignment()
878 .getAlignmentAnnotation()[rowIndex[0]];
880 int lastX = getColumnForXPos(fromX);
881 int currentX = getColumnForXPos(toX);
882 int fromXc = Math.min(lastX, currentX);
883 int toXc = Math.max(lastX, currentX);
884 ContactListI forFromX = av.getContactList(cma, fromXc);
885 ContactListI forToX = av.getContactList(cma, toXc);
887 if (forFromX != null && forToX != null)
889 ContactGeometry lastXcgeom = new ContactGeometry(forFromX,
891 ContactGeometry.contactInterval lastXci = lastXcgeom
892 .mapFor(rowIndex[1], rowIndex[1] - deltaY);
894 ContactGeometry cXcgeom = new ContactGeometry(forToX,
896 ContactGeometry.contactInterval cXci = cXcgeom.mapFor(rowIndex[1],
897 rowIndex[1] - deltaY);
899 // mark rectangular region formed by drag
900 System.err.println("Matrix Selection from last(" + fromXc + ",["
901 + lastXci.cStart + "," + lastXci.cEnd + "]) to cur(" + toXc
902 + ",[" + cXci.cStart + "," + cXci.cEnd + "])");
904 fr = Math.min(lastXci.cStart, lastXci.cEnd);
905 to = Math.max(lastXci.cStart, lastXci.cEnd);
906 System.err.println("Marking " + fr + " to " + to);
907 for (int c = fr; c <= to; c++)
909 if (cma.sequenceRef != null)
911 int col = cma.sequenceRef.findIndex(c);
912 av.getColumnSelection().addElement(col);
916 av.getColumnSelection().addElement(c);
919 fr = Math.min(cXci.cStart, cXci.cEnd);
920 to = Math.max(cXci.cStart, cXci.cEnd);
921 System.err.println("Marking " + fr + " to " + to);
922 for (int c = fr; c <= to; c++)
924 if (cma.sequenceRef != null)
926 int col = cma.sequenceRef.findIndex(c);
927 av.getColumnSelection().addElement(col);
931 av.getColumnSelection().addElement(c);
934 fr = Math.min(lastX, currentX);
935 to = Math.max(lastX, currentX);
937 System.err.println("Marking " + fr + " to " + to);
938 for (int c = fr; c <= to; c++)
940 av.getColumnSelection().addElement(c);
947 * Constructs the tooltip, and constructs and displays a status message, for
948 * the current mouse position
953 public void mouseMoved(MouseEvent evt)
955 int yPos = evt.getY();
956 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
957 int rowAndOffset[] = getRowIndexAndOffset(yPos, aa);
958 int row = rowAndOffset[0];
962 this.setToolTipText(null);
966 int column = getColumnForXPos(evt.getX());
968 AlignmentAnnotation ann = aa[row];
969 if (row > -1 && ann.annotations != null
970 && column < ann.annotations.length)
972 String toolTip = buildToolTip(ann, column, aa, rowAndOffset[1], av,
974 setToolTipText(toolTip == null ? null
975 : JvSwingUtils.wrapTooltip(true, toolTip));
976 String msg = getStatusMessage(av.getAlignment(), column, ann,
977 rowAndOffset[1], av);
978 ap.alignFrame.setStatus(msg);
982 this.setToolTipText(null);
983 ap.alignFrame.setStatus(" ");
987 private int getColumnForXPos(int x)
989 int column = (x / av.getCharWidth()) + av.getRanges().getStartRes();
990 column = Math.min(column, av.getRanges().getEndRes());
992 if (av.hasHiddenColumns())
994 column = av.getAlignment().getHiddenColumns()
995 .visibleToAbsoluteColumn(column);
1001 * Answers the index in the annotations array of the visible annotation at the
1002 * given y position. This is done by adding the heights of visible annotations
1003 * until the y position has been exceeded. Answers -1 if no annotations are
1004 * visible, or the y position is below all annotations.
1010 static int getRowIndex(int yPos, AlignmentAnnotation[] aa)
1016 return getRowIndexAndOffset(yPos, aa)[0];
1019 static int[] getRowIndexAndOffset(int yPos, AlignmentAnnotation[] aa)
1021 int[] res = new int[2];
1029 int height = 0, lheight = 0;
1030 for (int i = 0; i < aa.length; i++)
1035 height += aa[i].height;
1042 res[1] = height - yPos;
1050 * Answers a tooltip for the annotation at the current mouse position, not
1051 * wrapped in <html> tags (apply if wanted). Answers null if there is no
1057 * @param rowAndOffset
1059 static String buildToolTip(AlignmentAnnotation ann, int column,
1060 AlignmentAnnotation[] anns, int rowAndOffset, AlignViewportI av,
1063 String tooltip = null;
1064 if (ann.graphGroup > -1)
1066 StringBuilder tip = new StringBuilder(32);
1067 boolean first = true;
1068 for (int i = 0; i < anns.length; i++)
1070 if (anns[i].graphGroup == ann.graphGroup
1071 && anns[i].annotations[column] != null)
1078 tip.append(anns[i].label);
1079 String description = anns[i].annotations[column].description;
1080 if (description != null && description.length() > 0)
1082 tip.append(" ").append(description);
1086 tooltip = first ? null : tip.toString();
1088 else if (column < ann.annotations.length
1089 && ann.annotations[column] != null)
1091 tooltip = ann.annotations[column].description;
1093 // TODO abstract tooltip generator so different implementations can be built
1094 if (ann.graph == AlignmentAnnotation.CUSTOMRENDERER)
1096 ContactListI clist = av.getContactList(ann, column);
1099 ContactGeometry cgeom = new ContactGeometry(clist, ann.graphHeight);
1100 ContactGeometry.contactInterval ci = cgeom.mapFor(rowAndOffset);
1101 ContactRange cr = clist.getRangeFor(ci.cStart, ci.cEnd);
1102 tooltip = "Contact from " + clist.getPosition() + ", [" + ci.cStart
1103 + " - " + ci.cEnd + "]" + "<br/>Mean:" + cr.getMean();
1104 int col = ann.sequenceRef.findPosition(column);
1105 ap.getStructureSelectionManager()
1106 .highlightPositionsOn(ann.sequenceRef, new int[][]
1107 { new int[] { col, col },
1109 { ci.cStart, ci.cEnd } }, null);
1116 * Constructs and returns the status bar message
1121 * @param rowAndOffset
1123 static String getStatusMessage(AlignmentI al, int column,
1124 AlignmentAnnotation ann, int rowAndOffset, AlignViewportI av)
1127 * show alignment column and annotation description if any
1129 StringBuilder text = new StringBuilder(32);
1130 text.append(MessageManager.getString("label.column")).append(" ")
1131 .append(column + 1);
1133 if (column < ann.annotations.length && ann.annotations[column] != null)
1135 String description = ann.annotations[column].description;
1136 if (description != null && description.trim().length() > 0)
1138 text.append(" ").append(description);
1143 * if the annotation is sequence-specific, show the sequence number
1144 * in the alignment, and (if not a gap) the residue and position
1146 SequenceI seqref = ann.sequenceRef;
1149 int seqIndex = al.findIndex(seqref);
1152 text.append(", ").append(MessageManager.getString("label.sequence"))
1153 .append(" ").append(seqIndex + 1);
1154 char residue = seqref.getCharAt(column);
1155 if (!Comparison.isGap(residue))
1159 if (al.isNucleotide())
1161 name = ResidueProperties.nucleotideName
1162 .get(String.valueOf(residue));
1163 text.append(" Nucleotide: ")
1164 .append(name != null ? name : residue);
1168 name = 'X' == residue ? "X"
1169 : ('*' == residue ? "STOP"
1170 : ResidueProperties.aa2Triplet
1171 .get(String.valueOf(residue)));
1172 text.append(" Residue: ").append(name != null ? name : residue);
1174 int residuePos = seqref.findPosition(column);
1175 text.append(" (").append(residuePos).append(")");
1180 return text.toString();
1190 public void mouseClicked(MouseEvent evt)
1192 // if (activeRow != -1)
1194 // AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
1195 // AlignmentAnnotation anot = aa[activeRow];
1199 // TODO mouseClicked-content and drawCursor are quite experimental!
1200 public void drawCursor(Graphics graphics, SequenceI seq, int res, int x1,
1203 int pady = av.getCharHeight() / 5;
1205 graphics.setColor(Color.black);
1206 graphics.fillRect(x1, y1, av.getCharWidth(), av.getCharHeight());
1208 if (av.validCharWidth)
1210 graphics.setColor(Color.white);
1212 char s = seq.getCharAt(res);
1214 charOffset = (av.getCharWidth() - fm.charWidth(s)) / 2;
1215 graphics.drawString(String.valueOf(s), charOffset + x1,
1216 (y1 + av.getCharHeight()) - pady);
1221 private volatile boolean imageFresh = false;
1223 private Rectangle visibleRect = new Rectangle(),
1224 clipBounds = new Rectangle();
1233 public void paintComponent(Graphics g)
1236 // BH: note that this method is generally recommended to
1237 // call super.paintComponent(g). Otherwise, the children of this
1238 // component will not be rendered. That is not needed here
1239 // because AnnotationPanel does not have any children. It is
1240 // just a JPanel contained in a JViewPort.
1242 computeVisibleRect(visibleRect);
1244 g.setColor(Color.white);
1245 g.fillRect(0, 0, visibleRect.width, visibleRect.height);
1249 // BH 2018 optimizing generation of new Rectangle().
1251 || (visibleRect.width != (clipBounds = g
1252 .getClipBounds(clipBounds)).width)
1253 || (visibleRect.height != clipBounds.height))
1256 g.drawImage(image, 0, 0, this);
1261 imgWidth = (av.getRanges().getEndRes() - av.getRanges().getStartRes()
1262 + 1) * av.getCharWidth();
1268 if (image == null || imgWidth != image.getWidth(this)
1269 || image.getHeight(this) != getHeight())
1273 image = new BufferedImage(imgWidth,
1274 ap.getAnnotationPanel().getHeight(),
1275 BufferedImage.TYPE_INT_RGB);
1276 } catch (OutOfMemoryError oom)
1281 } catch (Exception x)
1286 "Couldn't allocate memory to redraw screen. Please restart Jalview",
1290 gg = (Graphics2D) image.getGraphics();
1294 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
1295 RenderingHints.VALUE_ANTIALIAS_ON);
1298 gg.setFont(av.getFont());
1299 fm = gg.getFontMetrics();
1300 gg.setColor(Color.white);
1301 gg.fillRect(0, 0, imgWidth, image.getHeight());
1306 gg = (Graphics2D) image.getGraphics();
1310 drawComponent(gg, av.getRanges().getStartRes(),
1311 av.getRanges().getEndRes() + 1);
1314 g.drawImage(image, 0, 0, this);
1318 * set true to enable redraw timing debug output on stderr
1320 private final boolean debugRedraw = false;
1323 * non-Thread safe repaint
1326 * repaint with horizontal shift in alignment
1328 public void fastPaint(int horizontal)
1330 if ((horizontal == 0) || image == null
1331 || av.getAlignment().getAlignmentAnnotation() == null
1332 || av.getAlignment().getAlignmentAnnotation().length < 1
1333 || av.isCalcInProgress())
1339 int sr = av.getRanges().getStartRes();
1340 int er = av.getRanges().getEndRes() + 1;
1343 Graphics2D gg = (Graphics2D) image.getGraphics();
1345 if (imgWidth > Math.abs(horizontal * av.getCharWidth()))
1347 // scroll is less than imgWidth away so can re-use buffered graphics
1348 gg.copyArea(0, 0, imgWidth, getHeight(),
1349 -horizontal * av.getCharWidth(), 0);
1351 if (horizontal > 0) // scrollbar pulled right, image to the left
1353 transX = (er - sr - horizontal) * av.getCharWidth();
1354 sr = er - horizontal;
1356 else if (horizontal < 0)
1358 er = sr - horizontal;
1361 gg.translate(transX, 0);
1363 drawComponent(gg, sr, er);
1365 gg.translate(-transX, 0);
1371 // Call repaint on alignment panel so that repaints from other alignment
1372 // panel components can be aggregated. Otherwise performance of the overview
1373 // window and others may be adversely affected.
1374 av.getAlignPanel().repaint();
1377 private volatile boolean lastImageGood = false;
1389 public void drawComponent(Graphics g, int startRes, int endRes)
1391 BufferedImage oldFaded = fadedImage;
1392 if (av.isCalcInProgress())
1396 lastImageGood = false;
1399 // We'll keep a record of the old image,
1400 // and draw a faded image until the calculation
1403 && (fadedImage == null || fadedImage.getWidth() != imgWidth
1404 || fadedImage.getHeight() != image.getHeight()))
1406 // System.err.println("redraw faded image ("+(fadedImage==null ?
1407 // "null image" : "") + " lastGood="+lastImageGood+")");
1408 fadedImage = new BufferedImage(imgWidth, image.getHeight(),
1409 BufferedImage.TYPE_INT_RGB);
1411 Graphics2D fadedG = (Graphics2D) fadedImage.getGraphics();
1413 fadedG.setColor(Color.white);
1414 fadedG.fillRect(0, 0, imgWidth, image.getHeight());
1416 fadedG.setComposite(
1417 AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .3f));
1418 fadedG.drawImage(image, 0, 0, this);
1421 // make sure we don't overwrite the last good faded image until all
1422 // calculations have finished
1423 lastImageGood = false;
1428 if (fadedImage != null)
1430 oldFaded = fadedImage;
1435 g.setColor(Color.white);
1436 g.fillRect(0, 0, (endRes - startRes) * av.getCharWidth(), getHeight());
1438 g.setFont(av.getFont());
1441 fm = g.getFontMetrics();
1444 if ((av.getAlignment().getAlignmentAnnotation() == null)
1445 || (av.getAlignment().getAlignmentAnnotation().length < 1))
1447 g.setColor(Color.white);
1448 g.fillRect(0, 0, getWidth(), getHeight());
1449 g.setColor(Color.black);
1450 if (av.validCharWidth)
1452 g.drawString(MessageManager
1453 .getString("label.alignment_has_no_annotations"), 20, 15);
1458 lastImageGood = renderer.drawComponent(this, av, g, activeRow, startRes,
1460 if (!lastImageGood && fadedImage == null)
1462 fadedImage = oldFaded;
1464 if (dragMode == DragMode.MatrixSelect)
1466 g.setColor(Color.yellow);
1467 g.drawRect(Math.min(firstDragX, mouseDragLastX),
1468 Math.min(firstDragY, mouseDragLastY),
1469 Math.max(firstDragX, mouseDragLastX)
1470 - Math.min(firstDragX, mouseDragLastX),
1471 Math.max(firstDragY, mouseDragLastY)
1472 - Math.min(firstDragY, mouseDragLastY));
1478 public FontMetrics getFontMetrics()
1484 public Image getFadedImage()
1490 public int getFadedImageWidth()
1495 private int[] bounds = new int[2];
1498 public int[] getVisibleVRange()
1500 if (ap != null && ap.getAlabels() != null)
1502 int sOffset = -ap.getAlabels().getScrollOffset();
1503 int visHeight = sOffset + ap.annotationSpaceFillerHolder.getHeight();
1504 bounds[0] = sOffset;
1505 bounds[1] = visHeight;
1515 * Try to ensure any references held are nulled
1517 public void dispose()
1527 * I created the renderer so I will dispose of it
1529 if (renderer != null)
1536 public void propertyChange(PropertyChangeEvent evt)
1538 // Respond to viewport range changes (e.g. alignment panel was scrolled)
1539 // Both scrolling and resizing change viewport ranges: scrolling changes
1540 // both start and end points, but resize only changes end values.
1541 // Here we only want to fastpaint on a scroll, with resize using a normal
1542 // paint, so scroll events are identified as changes to the horizontal or
1543 // vertical start value.
1544 if (evt.getPropertyName().equals(ViewportRanges.STARTRES))
1546 fastPaint((int) evt.getNewValue() - (int) evt.getOldValue());
1548 else if (evt.getPropertyName().equals(ViewportRanges.STARTRESANDSEQ))
1550 fastPaint(((int[]) evt.getNewValue())[0]
1551 - ((int[]) evt.getOldValue())[0]);
1553 else if (evt.getPropertyName().equals(ViewportRanges.MOVE_VIEWPORT))
1560 * computes the visible height of the annotation panel
1562 * @param adjustPanelHeight
1563 * - when false, just adjust existing height according to other
1565 * @param annotationHeight
1566 * @return height to use for the ScrollerPreferredVisibleSize
1568 public int adjustForAlignFrame(boolean adjustPanelHeight,
1569 int annotationHeight)
1572 * Estimate available height in the AlignFrame for alignment +
1573 * annotations. Deduct an estimate for title bar, menu bar, scale panel,
1574 * hscroll, status bar, insets.
1576 int stuff = (ap.getViewName() != null ? 30 : 0)
1577 + (Platform.isAMacAndNotJS() ? 120 : 140);
1578 int availableHeight = ap.alignFrame.getHeight() - stuff;
1579 int rowHeight = av.getCharHeight();
1581 if (adjustPanelHeight)
1583 int alignmentHeight = rowHeight * av.getAlignment().getHeight();
1586 * If not enough vertical space, maximize annotation height while keeping
1587 * at least two rows of alignment visible
1589 if (annotationHeight + alignmentHeight > availableHeight)
1591 annotationHeight = Math.min(annotationHeight,
1592 availableHeight - 2 * rowHeight);
1597 // maintain same window layout whilst updating sliders
1598 annotationHeight = Math.min(ap.annotationScroller.getSize().height,
1599 availableHeight - 2 * rowHeight);
1601 return annotationHeight;