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;
153 private boolean notJustOne;
156 * Creates a new AnnotationPanel object.
161 public AnnotationPanel(AlignmentPanel ap)
163 ToolTipManager.sharedInstance().registerComponent(this);
164 ToolTipManager.sharedInstance().setInitialDelay(0);
165 ToolTipManager.sharedInstance().setDismissDelay(10000);
168 this.setLayout(null);
169 addMouseListener(this);
170 addMouseMotionListener(this);
171 ap.annotationScroller.getVerticalScrollBar()
172 .addAdjustmentListener(this);
173 // save any wheel listeners on the scroller, so we can propagate scroll
175 _mwl = ap.annotationScroller.getMouseWheelListeners();
176 // and then set our own listener to consume all mousewheel events
177 ap.annotationScroller.addMouseWheelListener(this);
178 renderer = new AnnotationRenderer();
180 av.getRanges().addPropertyChangeListener(this);
183 public AnnotationPanel(AlignViewport av)
186 renderer = new AnnotationRenderer();
190 public void mouseWheelMoved(MouseWheelEvent e)
195 double wheelRotation = e.getPreciseWheelRotation();
196 if (wheelRotation > 0)
198 av.getRanges().scrollRight(true);
200 else if (wheelRotation < 0)
202 av.getRanges().scrollRight(false);
207 // TODO: find the correct way to let the event bubble up to
208 // ap.annotationScroller
209 for (MouseWheelListener mwl : _mwl)
213 mwl.mouseWheelMoved(e);
224 public Dimension getPreferredScrollableViewportSize()
226 Dimension ps = getPreferredSize();
227 return new Dimension(ps.width, adjustForAlignFrame(false, ps.height));
231 public int getScrollableBlockIncrement(Rectangle visibleRect,
232 int orientation, int direction)
238 public boolean getScrollableTracksViewportHeight()
244 public boolean getScrollableTracksViewportWidth()
250 public int getScrollableUnitIncrement(Rectangle visibleRect,
251 int orientation, int direction)
260 * java.awt.event.AdjustmentListener#adjustmentValueChanged(java.awt.event
264 public void adjustmentValueChanged(AdjustmentEvent evt)
266 // update annotation label display
267 ap.getAlabels().setScrollOffset(-evt.getValue());
271 * Calculates the height of the annotation displayed in the annotation panel.
272 * Callers should normally call the ap.adjustAnnotationHeight method to ensure
273 * all annotation associated components are updated correctly.
276 public int adjustPanelHeight()
278 int height = av.calcPanelHeight();
279 this.setPreferredSize(new Dimension(1, height));
282 // revalidate only when the alignment panel is fully constructed
296 public void actionPerformed(ActionEvent evt)
298 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
303 Annotation[] anot = aa[activeRow].annotations;
305 if (anot.length < av.getColumnSelection().getMax())
307 Annotation[] temp = new Annotation[av.getColumnSelection().getMax()
309 System.arraycopy(anot, 0, temp, 0, anot.length);
311 aa[activeRow].annotations = anot;
314 String action = evt.getActionCommand();
315 if (action.equals(REMOVE))
317 for (int index : av.getColumnSelection().getSelected())
319 if (av.getAlignment().getHiddenColumns().isVisible(index))
325 else if (action.equals(LABEL))
327 String exMesg = collectAnnotVals(anot, LABEL);
328 String label = JvOptionPane.showInputDialog(
329 MessageManager.getString("label.enter_label"), exMesg);
336 if ((label.length() > 0) && !aa[activeRow].hasText)
338 aa[activeRow].hasText = true;
341 for (int index : av.getColumnSelection().getSelected())
343 if (!av.getAlignment().getHiddenColumns().isVisible(index))
348 if (anot[index] == null)
350 anot[index] = new Annotation(label, "", ' ', 0);
354 anot[index].displayCharacter = label;
358 else if (action.equals(COLOUR))
360 final Annotation[] fAnot = anot;
361 String title = MessageManager
362 .getString("label.select_foreground_colour");
363 ColourChooserListener listener = new ColourChooserListener()
366 public void colourSelected(Color c)
368 HiddenColumns hiddenColumns = av.getAlignment()
370 for (int index : av.getColumnSelection().getSelected())
372 if (hiddenColumns.isVisible(index))
374 if (fAnot[index] == null)
376 fAnot[index] = new Annotation("", "", ' ', 0);
378 fAnot[index].colour = c;
383 JalviewColourChooser.showColourChooser(this, title, Color.black,
387 // HELIX, SHEET or STEM
390 String symbol = "\u03B1"; // alpha
392 if (action.equals(HELIX))
396 else if (action.equals(SHEET))
399 symbol = "\u03B2"; // beta
402 // Added by LML to color stems
403 else if (action.equals(STEM))
406 int column = av.getColumnSelection().getSelectedRanges().get(0)[0];
407 symbol = aa[activeRow].getDefaultRnaHelixSymbol(column);
410 if (!aa[activeRow].hasIcons)
412 aa[activeRow].hasIcons = true;
415 String label = JvOptionPane.showInputDialog(MessageManager
416 .getString("label.enter_label_for_the_structure"), symbol);
423 if ((label.length() > 0) && !aa[activeRow].hasText)
425 aa[activeRow].hasText = true;
426 if (action.equals(STEM))
428 aa[activeRow].showAllColLabels = true;
431 for (int index : av.getColumnSelection().getSelected())
433 if (!av.getAlignment().getHiddenColumns().isVisible(index))
438 if (anot[index] == null)
440 anot[index] = new Annotation(label, "", type, 0);
443 anot[index].secondaryStructure = type != 'S' ? type
444 : label.length() == 0 ? ' ' : label.charAt(0);
445 anot[index].displayCharacter = label;
450 av.getAlignment().validateAnnotation(aa[activeRow]);
451 ap.alignmentChanged();
452 ap.alignFrame.setMenusForViewport();
460 * Returns any existing annotation concatenated as a string. For each
461 * annotation, takes the description, if any, else the secondary structure
462 * character (if type is HELIX, SHEET or STEM), else the display character (if
469 private String collectAnnotVals(Annotation[] anots, String type)
471 // TODO is this method wanted? why? 'last' is not used
473 StringBuilder collatedInput = new StringBuilder(64);
475 ColumnSelection viscols = av.getColumnSelection();
476 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
479 * the selection list (read-only view) is in selection order, not
480 * column order; make a copy so we can sort it
482 List<Integer> selected = new ArrayList<>(viscols.getSelected());
483 Collections.sort(selected);
484 for (int index : selected)
486 // always check for current display state - just in case
487 if (!hidden.isVisible(index))
491 String tlabel = null;
492 if (anots[index] != null)
493 { // LML added stem code
494 if (type.equals(HELIX) || type.equals(SHEET) || type.equals(STEM)
495 || type.equals(LABEL))
497 tlabel = anots[index].description;
498 if (tlabel == null || tlabel.length() < 1)
500 if (type.equals(HELIX) || type.equals(SHEET)
501 || type.equals(STEM))
503 tlabel = "" + anots[index].secondaryStructure;
507 tlabel = "" + anots[index].displayCharacter;
511 if (tlabel != null && !tlabel.equals(last))
513 if (last.length() > 0)
515 collatedInput.append(" ");
517 collatedInput.append(tlabel);
521 return collatedInput.toString();
525 * Action on right mouse pressed on Mac is to show a pop-up menu for the
526 * annotation. Action on left mouse pressed is to find which annotation is
527 * pressed and mark the start of a column selection or graph resize operation.
532 public void mousePressed(MouseEvent evt)
535 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
540 mouseDragLastX = evt.getX();
541 mouseDragLastY = evt.getY();
544 * add visible annotation heights until we reach the y
545 * position, to find which annotation it is in
550 // todo could reuse getRowIndexAndOffset ?
551 final int y = evt.getY();
553 for (int i = 0; i < aa.length; i++)
557 height += aa[i].height;
566 else if (aa[i].graph != 0)
569 * we have clicked on a resizable graph annotation
572 yOffset = height - y;
579 * isPopupTrigger fires in mousePressed on Mac,
580 * not until mouseRelease on Windows
582 if (evt.isPopupTrigger() && activeRow != -1)
584 showPopupMenu(y, evt.getX());
588 if (graphStretch != -1)
591 if (aa[graphStretch].graph == AlignmentAnnotation.CUSTOMRENDERER)
593 if (evt.isAltDown() || evt.isAltGraphDown())
595 dragMode = DragMode.MatrixSelect;
596 firstDragX = mouseDragLastX;
597 firstDragY = mouseDragLastY;
601 int currentX = getColumnForXPos(evt.getX());
602 ContactListI forCurrentX = av.getContactList(aa[graphStretch],
604 if (forCurrentX != null)
606 ContactGeometry cXcgeom = new ContactGeometry(forCurrentX,
607 aa[graphStretch].graphHeight);
608 ContactGeometry.contactInterval cXci = cXcgeom.mapFor(yOffset,
611 fr = Math.min(cXci.cStart, cXci.cEnd);
612 to = Math.max(cXci.cStart, cXci.cEnd);
613 for (int c = fr; c <= to; c++)
615 av.getColumnSelection().addElement(c);
617 av.getColumnSelection().addElement(currentX);
624 ap.getScalePanel().mousePressed(evt);
629 * Construct and display a context menu at the right-click position
634 void showPopupMenu(final int y, int x)
636 if (av.getColumnSelection() == null
637 || av.getColumnSelection().isEmpty())
642 JPopupMenu pop = new JPopupMenu(
643 MessageManager.getString("label.structure_type"));
646 * Just display the needed structure options
648 if (av.getAlignment().isNucleotide())
650 item = new JMenuItem(STEM);
651 item.addActionListener(this);
656 item = new JMenuItem(HELIX);
657 item.addActionListener(this);
659 item = new JMenuItem(SHEET);
660 item.addActionListener(this);
663 item = new JMenuItem(LABEL);
664 item.addActionListener(this);
666 item = new JMenuItem(COLOUR);
667 item.addActionListener(this);
669 item = new JMenuItem(REMOVE);
670 item.addActionListener(this);
672 pop.show(this, x, y);
676 * Action on mouse up is to clear mouse drag data and call mouseReleased on
677 * ScalePanel, to deal with defining the selection group (if any) defined by
683 public void mouseReleased(MouseEvent evt)
685 if (dragMode == DragMode.MatrixSelect)
687 matrixSelectRange(evt);
694 mouseDragging = false;
695 if (dragMode == DragMode.Resize)
697 ap.adjustAnnotationHeight();
699 dragMode = DragMode.Undefined;
700 ap.getScalePanel().mouseReleased(evt);
703 * isPopupTrigger is set in mouseReleased on Windows
704 * (in mousePressed on Mac)
706 if (evt.isPopupTrigger() && activeRow != -1)
708 showPopupMenu(evt.getY(), evt.getX());
720 public void mouseEntered(MouseEvent evt)
722 this.mouseDragging = false;
723 ap.getScalePanel().mouseEntered(evt);
727 * On leaving the panel, calls ScalePanel.mouseExited to deal with scrolling
728 * with column selection on a mouse drag
733 public void mouseExited(MouseEvent evt)
735 ap.getScalePanel().mouseExited(evt);
739 * Action on starting or continuing a mouse drag. There are two possible
742 * <li>drag up or down on a graphed annotation increases or decreases the
743 * height of the graph</li>
744 * <li>dragging left or right selects the columns dragged across</li>
746 * A drag on a graph annotation is treated as column selection if it starts
747 * with more horizontal than vertical movement, and as resize if it starts
748 * with more vertical than horizontal movement. Once started, the drag does
754 public void mouseDragged(MouseEvent evt)
757 * if dragMode is Undefined:
758 * - set to Select if dx > dy
759 * - set to Resize if dy > dx
760 * - do nothing if dx == dy
762 final int x = evt.getX();
763 final int y = evt.getY();
764 if (dragMode == DragMode.Undefined)
766 int dx = Math.abs(x - mouseDragLastX);
767 int dy = Math.abs(y - mouseDragLastY);
768 if (graphStretch == -1 || dx > dy)
771 * mostly horizontal drag, or not a graph annotation
773 dragMode = DragMode.Select;
778 * mostly vertical drag
780 dragMode = DragMode.Resize;
781 notJustOne = evt.isShiftDown();
784 * but could also be a matrix drag
786 if ((evt.isAltDown() || evt.isAltGraphDown()) && (av.getAlignment()
787 .getAlignmentAnnotation()[graphStretch].graph == AlignmentAnnotation.CUSTOMRENDERER))
790 * dragging in a matrix
792 dragMode = DragMode.MatrixSelect;
793 firstDragX = mouseDragLastX;
794 firstDragY = mouseDragLastY;
799 if (dragMode == DragMode.Undefined)
803 * drag is diagonal - defer deciding whether to
804 * treat as up/down or left/right
811 if (dragMode == DragMode.Resize)
814 * resize graph annotation if mouse was dragged up or down
816 int deltaY = mouseDragLastY - evt.getY();
819 AlignmentAnnotation graphAnnotation = av.getAlignment()
820 .getAlignmentAnnotation()[graphStretch];
821 int newHeight = Math.max(0, graphAnnotation.graphHeight + deltaY);
824 for (AlignmentAnnotation similar : av.getAlignment()
825 .findAnnotations(null, graphAnnotation.getCalcId(),
826 graphAnnotation.label))
828 similar.graphHeight = newHeight;
834 graphAnnotation.graphHeight = newHeight;
837 ap.paintAlignment(false, false);
840 else if (dragMode == DragMode.MatrixSelect)
843 * TODO draw a rubber band for range
847 ap.paintAlignment(false, false);
852 * for mouse drag left or right, delegate to
853 * ScalePanel to adjust the column selection
855 ap.getScalePanel().mouseDragged(evt);
864 public void matrixSelectRange(MouseEvent evt)
867 * get geometry of drag
869 int fromY = Math.min(firstDragY, evt.getY());
870 int toY = Math.max(firstDragY, evt.getY());
871 int fromX = Math.min(firstDragX, evt.getX());
872 int toX = Math.max(firstDragX, evt.getX());
874 int deltaY = toY - fromY;
875 int deltaX = toX - fromX;
877 int[] rowIndex = getRowIndexAndOffset(fromY,
878 av.getAlignment().getAlignmentAnnotation());
879 int[] toRowIndex = getRowIndexAndOffset(toY,
880 av.getAlignment().getAlignmentAnnotation());
882 if (rowIndex == null || toRowIndex == null)
884 System.out.println("Drag out of range. needs to be clipped");
887 if (rowIndex[0] != toRowIndex[0])
889 System.out.println("Drag went to another row. needs to be clipped");
892 // rectangular selection on matrix style annotation
893 AlignmentAnnotation cma = av.getAlignment()
894 .getAlignmentAnnotation()[rowIndex[0]];
896 int lastX = getColumnForXPos(fromX);
897 int currentX = getColumnForXPos(toX);
898 int fromXc = Math.min(lastX, currentX);
899 int toXc = Math.max(lastX, currentX);
900 ContactListI forFromX = av.getContactList(cma, fromXc);
901 ContactListI forToX = av.getContactList(cma, toXc);
903 if (forFromX != null && forToX != null)
905 ContactGeometry lastXcgeom = new ContactGeometry(forFromX,
907 ContactGeometry.contactInterval lastXci = lastXcgeom
908 .mapFor(rowIndex[1], rowIndex[1] - deltaY);
910 ContactGeometry cXcgeom = new ContactGeometry(forToX,
912 ContactGeometry.contactInterval cXci = cXcgeom.mapFor(rowIndex[1],
913 rowIndex[1] - deltaY);
915 // mark rectangular region formed by drag
916 System.err.println("Matrix Selection from last(" + fromXc + ",["
917 + lastXci.cStart + "," + lastXci.cEnd + "]) to cur(" + toXc
918 + ",[" + cXci.cStart + "," + cXci.cEnd + "])");
920 fr = Math.min(lastXci.cStart, lastXci.cEnd);
921 to = Math.max(lastXci.cStart, lastXci.cEnd);
922 System.err.println("Marking " + fr + " to " + to);
923 for (int c = fr; c <= to; c++)
925 if (cma.sequenceRef != null)
927 int col = cma.sequenceRef.findIndex(c);
928 av.getColumnSelection().addElement(col);
932 av.getColumnSelection().addElement(c);
935 fr = Math.min(cXci.cStart, cXci.cEnd);
936 to = Math.max(cXci.cStart, cXci.cEnd);
937 System.err.println("Marking " + fr + " to " + to);
938 for (int c = fr; c <= to; c++)
940 if (cma.sequenceRef != null)
942 int col = cma.sequenceRef.findIndex(c);
943 av.getColumnSelection().addElement(col);
947 av.getColumnSelection().addElement(c);
950 fr = Math.min(lastX, currentX);
951 to = Math.max(lastX, currentX);
953 System.err.println("Marking " + fr + " to " + to);
954 for (int c = fr; c <= to; c++)
956 av.getColumnSelection().addElement(c);
963 * Constructs the tooltip, and constructs and displays a status message, for
964 * the current mouse position
969 public void mouseMoved(MouseEvent evt)
971 int yPos = evt.getY();
972 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
973 int rowAndOffset[] = getRowIndexAndOffset(yPos, aa);
974 int row = rowAndOffset[0];
978 this.setToolTipText(null);
982 int column = getColumnForXPos(evt.getX());
984 AlignmentAnnotation ann = aa[row];
985 if (row > -1 && ann.annotations != null
986 && column < ann.annotations.length)
988 String toolTip = buildToolTip(ann, column, aa, rowAndOffset[1], av,
990 setToolTipText(toolTip == null ? null
991 : JvSwingUtils.wrapTooltip(true, toolTip));
992 String msg = getStatusMessage(av.getAlignment(), column, ann,
993 rowAndOffset[1], av);
994 ap.alignFrame.setStatus(msg);
998 this.setToolTipText(null);
999 ap.alignFrame.setStatus(" ");
1003 private int getColumnForXPos(int x)
1005 int column = (x / av.getCharWidth()) + av.getRanges().getStartRes();
1006 column = Math.min(column, av.getRanges().getEndRes());
1008 if (av.hasHiddenColumns())
1010 column = av.getAlignment().getHiddenColumns()
1011 .visibleToAbsoluteColumn(column);
1017 * Answers the index in the annotations array of the visible annotation at the
1018 * given y position. This is done by adding the heights of visible annotations
1019 * until the y position has been exceeded. Answers -1 if no annotations are
1020 * visible, or the y position is below all annotations.
1026 static int getRowIndex(int yPos, AlignmentAnnotation[] aa)
1032 return getRowIndexAndOffset(yPos, aa)[0];
1035 static int[] getRowIndexAndOffset(int yPos, AlignmentAnnotation[] aa)
1037 int[] res = new int[2];
1045 int height = 0, lheight = 0;
1046 for (int i = 0; i < aa.length; i++)
1051 height += aa[i].height;
1058 res[1] = height - yPos;
1066 * Answers a tooltip for the annotation at the current mouse position, not
1067 * wrapped in <html> tags (apply if wanted). Answers null if there is no
1073 * @param rowAndOffset
1075 static String buildToolTip(AlignmentAnnotation ann, int column,
1076 AlignmentAnnotation[] anns, int rowAndOffset, AlignViewportI av,
1079 String tooltip = null;
1080 if (ann.graphGroup > -1)
1082 StringBuilder tip = new StringBuilder(32);
1083 boolean first = true;
1084 for (int i = 0; i < anns.length; i++)
1086 if (anns[i].graphGroup == ann.graphGroup
1087 && anns[i].annotations[column] != null)
1094 tip.append(anns[i].label);
1095 String description = anns[i].annotations[column].description;
1096 if (description != null && description.length() > 0)
1098 tip.append(" ").append(description);
1102 tooltip = first ? null : tip.toString();
1104 else if (column < ann.annotations.length
1105 && ann.annotations[column] != null)
1107 tooltip = ann.annotations[column].description;
1109 // TODO abstract tooltip generator so different implementations can be built
1110 if (ann.graph == AlignmentAnnotation.CUSTOMRENDERER)
1112 ContactListI clist = av.getContactList(ann, column);
1115 ContactGeometry cgeom = new ContactGeometry(clist, ann.graphHeight);
1116 ContactGeometry.contactInterval ci = cgeom.mapFor(rowAndOffset);
1117 ContactRange cr = clist.getRangeFor(ci.cStart, ci.cEnd);
1118 tooltip = "Contact from " + clist.getPosition() + ", [" + ci.cStart
1119 + " - " + ci.cEnd + "]" + "<br/>Mean:" + cr.getMean();
1120 int col = ann.sequenceRef.findPosition(column);
1121 ap.getStructureSelectionManager()
1122 .highlightPositionsOn(ann.sequenceRef, new int[][]
1123 { new int[] { col, col },
1125 { ci.cStart, ci.cEnd } }, null);
1132 * Constructs and returns the status bar message
1137 * @param rowAndOffset
1139 static String getStatusMessage(AlignmentI al, int column,
1140 AlignmentAnnotation ann, int rowAndOffset, AlignViewportI av)
1143 * show alignment column and annotation description if any
1145 StringBuilder text = new StringBuilder(32);
1146 text.append(MessageManager.getString("label.column")).append(" ")
1147 .append(column + 1);
1149 if (column < ann.annotations.length && ann.annotations[column] != null)
1151 String description = ann.annotations[column].description;
1152 if (description != null && description.trim().length() > 0)
1154 text.append(" ").append(description);
1159 * if the annotation is sequence-specific, show the sequence number
1160 * in the alignment, and (if not a gap) the residue and position
1162 SequenceI seqref = ann.sequenceRef;
1165 int seqIndex = al.findIndex(seqref);
1168 text.append(", ").append(MessageManager.getString("label.sequence"))
1169 .append(" ").append(seqIndex + 1);
1170 char residue = seqref.getCharAt(column);
1171 if (!Comparison.isGap(residue))
1175 if (al.isNucleotide())
1177 name = ResidueProperties.nucleotideName
1178 .get(String.valueOf(residue));
1179 text.append(" Nucleotide: ")
1180 .append(name != null ? name : residue);
1184 name = 'X' == residue ? "X"
1185 : ('*' == residue ? "STOP"
1186 : ResidueProperties.aa2Triplet
1187 .get(String.valueOf(residue)));
1188 text.append(" Residue: ").append(name != null ? name : residue);
1190 int residuePos = seqref.findPosition(column);
1191 text.append(" (").append(residuePos).append(")");
1196 return text.toString();
1206 public void mouseClicked(MouseEvent evt)
1208 // if (activeRow != -1)
1210 // AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
1211 // AlignmentAnnotation anot = aa[activeRow];
1215 // TODO mouseClicked-content and drawCursor are quite experimental!
1216 public void drawCursor(Graphics graphics, SequenceI seq, int res, int x1,
1219 int pady = av.getCharHeight() / 5;
1221 graphics.setColor(Color.black);
1222 graphics.fillRect(x1, y1, av.getCharWidth(), av.getCharHeight());
1224 if (av.validCharWidth)
1226 graphics.setColor(Color.white);
1228 char s = seq.getCharAt(res);
1230 charOffset = (av.getCharWidth() - fm.charWidth(s)) / 2;
1231 graphics.drawString(String.valueOf(s), charOffset + x1,
1232 (y1 + av.getCharHeight()) - pady);
1237 private volatile boolean imageFresh = false;
1239 private Rectangle visibleRect = new Rectangle(),
1240 clipBounds = new Rectangle();
1249 public void paintComponent(Graphics g)
1252 // BH: note that this method is generally recommended to
1253 // call super.paintComponent(g). Otherwise, the children of this
1254 // component will not be rendered. That is not needed here
1255 // because AnnotationPanel does not have any children. It is
1256 // just a JPanel contained in a JViewPort.
1258 computeVisibleRect(visibleRect);
1260 g.setColor(Color.white);
1261 g.fillRect(0, 0, visibleRect.width, visibleRect.height);
1265 // BH 2018 optimizing generation of new Rectangle().
1267 || (visibleRect.width != (clipBounds = g
1268 .getClipBounds(clipBounds)).width)
1269 || (visibleRect.height != clipBounds.height))
1272 g.drawImage(image, 0, 0, this);
1277 imgWidth = (av.getRanges().getEndRes() - av.getRanges().getStartRes()
1278 + 1) * av.getCharWidth();
1284 if (image == null || imgWidth != image.getWidth(this)
1285 || image.getHeight(this) != getHeight())
1289 image = new BufferedImage(imgWidth,
1290 ap.getAnnotationPanel().getHeight(),
1291 BufferedImage.TYPE_INT_RGB);
1292 } catch (OutOfMemoryError oom)
1297 } catch (Exception x)
1302 "Couldn't allocate memory to redraw screen. Please restart Jalview",
1306 gg = (Graphics2D) image.getGraphics();
1310 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
1311 RenderingHints.VALUE_ANTIALIAS_ON);
1314 gg.setFont(av.getFont());
1315 fm = gg.getFontMetrics();
1316 gg.setColor(Color.white);
1317 gg.fillRect(0, 0, imgWidth, image.getHeight());
1322 gg = (Graphics2D) image.getGraphics();
1326 drawComponent(gg, av.getRanges().getStartRes(),
1327 av.getRanges().getEndRes() + 1);
1330 g.drawImage(image, 0, 0, this);
1334 * set true to enable redraw timing debug output on stderr
1336 private final boolean debugRedraw = false;
1339 * non-Thread safe repaint
1342 * repaint with horizontal shift in alignment
1344 public void fastPaint(int horizontal)
1346 if ((horizontal == 0) || image == null
1347 || av.getAlignment().getAlignmentAnnotation() == null
1348 || av.getAlignment().getAlignmentAnnotation().length < 1
1349 || av.isCalcInProgress())
1355 int sr = av.getRanges().getStartRes();
1356 int er = av.getRanges().getEndRes() + 1;
1359 Graphics2D gg = (Graphics2D) image.getGraphics();
1361 if (imgWidth > Math.abs(horizontal * av.getCharWidth()))
1363 // scroll is less than imgWidth away so can re-use buffered graphics
1364 gg.copyArea(0, 0, imgWidth, getHeight(),
1365 -horizontal * av.getCharWidth(), 0);
1367 if (horizontal > 0) // scrollbar pulled right, image to the left
1369 transX = (er - sr - horizontal) * av.getCharWidth();
1370 sr = er - horizontal;
1372 else if (horizontal < 0)
1374 er = sr - horizontal;
1377 gg.translate(transX, 0);
1379 drawComponent(gg, sr, er);
1381 gg.translate(-transX, 0);
1387 // Call repaint on alignment panel so that repaints from other alignment
1388 // panel components can be aggregated. Otherwise performance of the overview
1389 // window and others may be adversely affected.
1390 av.getAlignPanel().repaint();
1393 private volatile boolean lastImageGood = false;
1405 public void drawComponent(Graphics g, int startRes, int endRes)
1407 BufferedImage oldFaded = fadedImage;
1408 if (av.isCalcInProgress())
1412 lastImageGood = false;
1415 // We'll keep a record of the old image,
1416 // and draw a faded image until the calculation
1419 && (fadedImage == null || fadedImage.getWidth() != imgWidth
1420 || fadedImage.getHeight() != image.getHeight()))
1422 // System.err.println("redraw faded image ("+(fadedImage==null ?
1423 // "null image" : "") + " lastGood="+lastImageGood+")");
1424 fadedImage = new BufferedImage(imgWidth, image.getHeight(),
1425 BufferedImage.TYPE_INT_RGB);
1427 Graphics2D fadedG = (Graphics2D) fadedImage.getGraphics();
1429 fadedG.setColor(Color.white);
1430 fadedG.fillRect(0, 0, imgWidth, image.getHeight());
1432 fadedG.setComposite(
1433 AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .3f));
1434 fadedG.drawImage(image, 0, 0, this);
1437 // make sure we don't overwrite the last good faded image until all
1438 // calculations have finished
1439 lastImageGood = false;
1444 if (fadedImage != null)
1446 oldFaded = fadedImage;
1451 g.setColor(Color.white);
1452 g.fillRect(0, 0, (endRes - startRes) * av.getCharWidth(), getHeight());
1454 g.setFont(av.getFont());
1457 fm = g.getFontMetrics();
1460 if ((av.getAlignment().getAlignmentAnnotation() == null)
1461 || (av.getAlignment().getAlignmentAnnotation().length < 1))
1463 g.setColor(Color.white);
1464 g.fillRect(0, 0, getWidth(), getHeight());
1465 g.setColor(Color.black);
1466 if (av.validCharWidth)
1468 g.drawString(MessageManager
1469 .getString("label.alignment_has_no_annotations"), 20, 15);
1474 lastImageGood = renderer.drawComponent(this, av, g, activeRow, startRes,
1476 if (!lastImageGood && fadedImage == null)
1478 fadedImage = oldFaded;
1480 if (dragMode == DragMode.MatrixSelect)
1482 g.setColor(Color.yellow);
1483 g.drawRect(Math.min(firstDragX, mouseDragLastX),
1484 Math.min(firstDragY, mouseDragLastY),
1485 Math.max(firstDragX, mouseDragLastX)
1486 - Math.min(firstDragX, mouseDragLastX),
1487 Math.max(firstDragY, mouseDragLastY)
1488 - Math.min(firstDragY, mouseDragLastY));
1494 public FontMetrics getFontMetrics()
1500 public Image getFadedImage()
1506 public int getFadedImageWidth()
1511 private int[] bounds = new int[2];
1514 public int[] getVisibleVRange()
1516 if (ap != null && ap.getAlabels() != null)
1518 int sOffset = -ap.getAlabels().getScrollOffset();
1519 int visHeight = sOffset + ap.annotationSpaceFillerHolder.getHeight();
1520 bounds[0] = sOffset;
1521 bounds[1] = visHeight;
1531 * Try to ensure any references held are nulled
1533 public void dispose()
1543 * I created the renderer so I will dispose of it
1545 if (renderer != null)
1552 public void propertyChange(PropertyChangeEvent evt)
1554 // Respond to viewport range changes (e.g. alignment panel was scrolled)
1555 // Both scrolling and resizing change viewport ranges: scrolling changes
1556 // both start and end points, but resize only changes end values.
1557 // Here we only want to fastpaint on a scroll, with resize using a normal
1558 // paint, so scroll events are identified as changes to the horizontal or
1559 // vertical start value.
1560 if (evt.getPropertyName().equals(ViewportRanges.STARTRES))
1562 fastPaint((int) evt.getNewValue() - (int) evt.getOldValue());
1564 else if (evt.getPropertyName().equals(ViewportRanges.STARTRESANDSEQ))
1566 fastPaint(((int[]) evt.getNewValue())[0]
1567 - ((int[]) evt.getOldValue())[0]);
1569 else if (evt.getPropertyName().equals(ViewportRanges.MOVE_VIEWPORT))
1576 * computes the visible height of the annotation panel
1578 * @param adjustPanelHeight
1579 * - when false, just adjust existing height according to other
1581 * @param annotationHeight
1582 * @return height to use for the ScrollerPreferredVisibleSize
1584 public int adjustForAlignFrame(boolean adjustPanelHeight,
1585 int annotationHeight)
1588 * Estimate available height in the AlignFrame for alignment +
1589 * annotations. Deduct an estimate for title bar, menu bar, scale panel,
1590 * hscroll, status bar, insets.
1592 int stuff = (ap.getViewName() != null ? 30 : 0)
1593 + (Platform.isAMacAndNotJS() ? 120 : 140);
1594 int availableHeight = ap.alignFrame.getHeight() - stuff;
1595 int rowHeight = av.getCharHeight();
1597 if (adjustPanelHeight)
1599 int alignmentHeight = rowHeight * av.getAlignment().getHeight();
1602 * If not enough vertical space, maximize annotation height while keeping
1603 * at least two rows of alignment visible
1605 if (annotationHeight + alignmentHeight > availableHeight)
1607 annotationHeight = Math.min(annotationHeight,
1608 availableHeight - 2 * rowHeight);
1613 // maintain same window layout whilst updating sliders
1614 annotationHeight = Math.min(ap.annotationScroller.getSize().height,
1615 availableHeight - 2 * rowHeight);
1617 return annotationHeight;