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.GraphLine;
61 import jalview.datamodel.HiddenColumns;
62 import jalview.datamodel.SequenceI;
63 import jalview.gui.JalviewColourChooser.ColourChooserListener;
64 import jalview.renderer.AnnotationRenderer;
65 import jalview.renderer.AwtRenderPanelI;
66 import jalview.renderer.ContactGeometry;
67 import jalview.schemes.ResidueProperties;
68 import jalview.util.Comparison;
69 import jalview.util.MessageManager;
70 import jalview.util.Platform;
71 import jalview.viewmodel.ViewportListenerI;
72 import jalview.viewmodel.ViewportRanges;
73 import jalview.ws.datamodel.alphafold.PAEContactMatrix;
76 * AnnotationPanel displays visible portion of annotation rows below unwrapped
82 public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
83 MouseListener, MouseWheelListener, MouseMotionListener,
84 ActionListener, AdjustmentListener, Scrollable, ViewportListenerI
88 Select, Resize, Undefined, MatrixSelect
91 String HELIX = MessageManager.getString("label.helix");
93 String SHEET = MessageManager.getString("label.sheet");
96 * For RNA secondary structure "stems" aka helices
98 String STEM = MessageManager.getString("label.rna_helix");
100 String LABEL = MessageManager.getString("label.label");
102 String REMOVE = MessageManager.getString("label.remove_annotation");
104 String COLOUR = MessageManager.getString("action.colour");
106 public final Color HELIX_COLOUR = Color.red.darker();
108 public final Color SHEET_COLOUR = Color.green.darker().darker();
110 public final Color STEM_COLOUR = Color.blue.darker();
113 public AlignViewport av;
117 public int activeRow = -1;
119 public BufferedImage image;
121 public volatile BufferedImage fadedImage;
123 // private Graphics2D gg;
125 public FontMetrics fm;
127 public int imgWidth = 0;
129 boolean fastPaint = false;
131 // Used For mouse Dragging and resizing graphs
132 int graphStretch = -1;
134 int mouseDragLastX = -1;
136 int mouseDragLastY = -1;
142 DragMode dragMode = DragMode.Undefined;
144 boolean mouseDragging = false;
146 // for editing cursor
151 public final AnnotationRenderer renderer;
153 private MouseWheelListener[] _mwl;
155 private boolean notJustOne;
158 * Creates a new AnnotationPanel object.
163 public AnnotationPanel(AlignmentPanel ap)
165 ToolTipManager.sharedInstance().registerComponent(this);
166 ToolTipManager.sharedInstance().setInitialDelay(0);
167 ToolTipManager.sharedInstance().setDismissDelay(10000);
170 this.setLayout(null);
171 addMouseListener(this);
172 addMouseMotionListener(this);
173 ap.annotationScroller.getVerticalScrollBar()
174 .addAdjustmentListener(this);
175 // save any wheel listeners on the scroller, so we can propagate scroll
177 _mwl = ap.annotationScroller.getMouseWheelListeners();
178 // and then set our own listener to consume all mousewheel events
179 ap.annotationScroller.addMouseWheelListener(this);
180 renderer = new AnnotationRenderer();
182 av.getRanges().addPropertyChangeListener(this);
185 public AnnotationPanel(AlignViewport av)
188 renderer = new AnnotationRenderer();
192 public void mouseWheelMoved(MouseWheelEvent e)
197 double wheelRotation = e.getPreciseWheelRotation();
198 if (wheelRotation > 0)
200 av.getRanges().scrollRight(true);
202 else if (wheelRotation < 0)
204 av.getRanges().scrollRight(false);
209 // TODO: find the correct way to let the event bubble up to
210 // ap.annotationScroller
211 for (MouseWheelListener mwl : _mwl)
215 mwl.mouseWheelMoved(e);
226 public Dimension getPreferredScrollableViewportSize()
228 Dimension ps = getPreferredSize();
229 return new Dimension(ps.width, adjustForAlignFrame(false, ps.height));
233 public int getScrollableBlockIncrement(Rectangle visibleRect,
234 int orientation, int direction)
240 public boolean getScrollableTracksViewportHeight()
246 public boolean getScrollableTracksViewportWidth()
252 public int getScrollableUnitIncrement(Rectangle visibleRect,
253 int orientation, int direction)
262 * java.awt.event.AdjustmentListener#adjustmentValueChanged(java.awt.event
266 public void adjustmentValueChanged(AdjustmentEvent evt)
268 // update annotation label display
269 ap.getAlabels().setScrollOffset(-evt.getValue());
273 * Calculates the height of the annotation displayed in the annotation panel.
274 * Callers should normally call the ap.adjustAnnotationHeight method to ensure
275 * all annotation associated components are updated correctly.
278 public int adjustPanelHeight()
280 int height = av.calcPanelHeight();
281 this.setPreferredSize(new Dimension(1, height));
284 // revalidate only when the alignment panel is fully constructed
298 public void actionPerformed(ActionEvent evt)
300 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
305 Annotation[] anot = aa[activeRow].annotations;
307 if (anot.length < av.getColumnSelection().getMax())
309 Annotation[] temp = new Annotation[av.getColumnSelection().getMax()
311 System.arraycopy(anot, 0, temp, 0, anot.length);
313 aa[activeRow].annotations = anot;
316 String action = evt.getActionCommand();
317 if (action.equals(REMOVE))
319 for (int index : av.getColumnSelection().getSelected())
321 if (av.getAlignment().getHiddenColumns().isVisible(index))
327 else if (action.equals(LABEL))
329 String exMesg = collectAnnotVals(anot, LABEL);
330 String label = JvOptionPane.showInputDialog(
331 MessageManager.getString("label.enter_label"), exMesg);
338 if ((label.length() > 0) && !aa[activeRow].hasText)
340 aa[activeRow].hasText = true;
343 for (int index : av.getColumnSelection().getSelected())
345 if (!av.getAlignment().getHiddenColumns().isVisible(index))
350 if (anot[index] == null)
352 anot[index] = new Annotation(label, "", ' ', 0);
356 anot[index].displayCharacter = label;
360 else if (action.equals(COLOUR))
362 final Annotation[] fAnot = anot;
363 String title = MessageManager
364 .getString("label.select_foreground_colour");
365 ColourChooserListener listener = new ColourChooserListener()
368 public void colourSelected(Color c)
370 HiddenColumns hiddenColumns = av.getAlignment()
372 for (int index : av.getColumnSelection().getSelected())
374 if (hiddenColumns.isVisible(index))
376 if (fAnot[index] == null)
378 fAnot[index] = new Annotation("", "", ' ', 0);
380 fAnot[index].colour = c;
385 JalviewColourChooser.showColourChooser(this, title, Color.black,
389 // HELIX, SHEET or STEM
392 String symbol = "\u03B1"; // alpha
394 if (action.equals(HELIX))
398 else if (action.equals(SHEET))
401 symbol = "\u03B2"; // beta
404 // Added by LML to color stems
405 else if (action.equals(STEM))
408 int column = av.getColumnSelection().getSelectedRanges().get(0)[0];
409 symbol = aa[activeRow].getDefaultRnaHelixSymbol(column);
412 if (!aa[activeRow].hasIcons)
414 aa[activeRow].hasIcons = true;
417 String label = JvOptionPane.showInputDialog(MessageManager
418 .getString("label.enter_label_for_the_structure"), symbol);
425 if ((label.length() > 0) && !aa[activeRow].hasText)
427 aa[activeRow].hasText = true;
428 if (action.equals(STEM))
430 aa[activeRow].showAllColLabels = true;
433 for (int index : av.getColumnSelection().getSelected())
435 if (!av.getAlignment().getHiddenColumns().isVisible(index))
440 if (anot[index] == null)
442 anot[index] = new Annotation(label, "", type, 0);
445 anot[index].secondaryStructure = type != 'S' ? type
446 : label.length() == 0 ? ' ' : label.charAt(0);
447 anot[index].displayCharacter = label;
452 av.getAlignment().validateAnnotation(aa[activeRow]);
453 ap.alignmentChanged();
454 ap.alignFrame.setMenusForViewport();
462 * Returns any existing annotation concatenated as a string. For each
463 * annotation, takes the description, if any, else the secondary structure
464 * character (if type is HELIX, SHEET or STEM), else the display character (if
471 private String collectAnnotVals(Annotation[] anots, String type)
473 // TODO is this method wanted? why? 'last' is not used
475 StringBuilder collatedInput = new StringBuilder(64);
477 ColumnSelection viscols = av.getColumnSelection();
478 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
481 * the selection list (read-only view) is in selection order, not
482 * column order; make a copy so we can sort it
484 List<Integer> selected = new ArrayList<>(viscols.getSelected());
485 Collections.sort(selected);
486 for (int index : selected)
488 // always check for current display state - just in case
489 if (!hidden.isVisible(index))
493 String tlabel = null;
494 if (anots[index] != null)
495 { // LML added stem code
496 if (type.equals(HELIX) || type.equals(SHEET) || type.equals(STEM)
497 || type.equals(LABEL))
499 tlabel = anots[index].description;
500 if (tlabel == null || tlabel.length() < 1)
502 if (type.equals(HELIX) || type.equals(SHEET)
503 || type.equals(STEM))
505 tlabel = "" + anots[index].secondaryStructure;
509 tlabel = "" + anots[index].displayCharacter;
513 if (tlabel != null && !tlabel.equals(last))
515 if (last.length() > 0)
517 collatedInput.append(" ");
519 collatedInput.append(tlabel);
523 return collatedInput.toString();
527 * Action on right mouse pressed on Mac is to show a pop-up menu for the
528 * annotation. Action on left mouse pressed is to find which annotation is
529 * pressed and mark the start of a column selection or graph resize operation.
534 public void mousePressed(MouseEvent evt)
537 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
542 mouseDragLastX = evt.getX();
543 mouseDragLastY = evt.getY();
546 * add visible annotation heights until we reach the y
547 * position, to find which annotation it is in
552 // todo could reuse getRowIndexAndOffset ?
553 final int y = evt.getY();
555 for (int i = 0; i < aa.length; i++)
559 height += aa[i].height;
568 else if (aa[i].graph != 0)
571 * we have clicked on a resizable graph annotation
574 yOffset = height - y;
581 * isPopupTrigger fires in mousePressed on Mac,
582 * not until mouseRelease on Windows
584 if (evt.isPopupTrigger() && activeRow != -1)
586 showPopupMenu(y, evt.getX());
590 if (graphStretch != -1)
593 if (aa[graphStretch].graph == AlignmentAnnotation.CONTACT_MAP)
595 // data in row has position on y as well as x axis
596 if (evt.isAltDown() || evt.isAltGraphDown())
598 dragMode = DragMode.MatrixSelect;
599 firstDragX = mouseDragLastX;
600 firstDragY = mouseDragLastY;
604 GraphLine thr = aa[graphStretch].getThreshold();
605 int currentX = getColumnForXPos(evt.getX());
606 ContactListI forCurrentX = av.getContactList(aa[graphStretch],
608 if (forCurrentX != null)
610 ContactGeometry cXcgeom = new ContactGeometry(forCurrentX,
611 aa[graphStretch].graphHeight);
612 ContactGeometry.contactInterval cXci = cXcgeom.mapFor(yOffset,
615 fr = Math.min(cXci.cStart, cXci.cEnd);
616 to = Math.max(cXci.cStart, cXci.cEnd);
617 // select corresponding range in segment under mouse
619 for (int c = fr; c <= to; c++)
621 av.getColumnSelection().addElement(c);
623 av.getColumnSelection().addElement(currentX);
626 // and also select everything lower than the max range adjacent
628 if (PAEContactMatrix.PAEMATRIX.equals(aa[graphStretch].getCalcId()))
631 ContactRange cr = forCurrentX.getRangeFor(fr, to);
633 // TODO: could use GraphLine instead of arbitrary picking
634 // TODO: could report mean/median/variance for partitions (contiguous selected vs unselected regions and inter-contig regions)
635 // controls feathering - what other elements in row/column should we select
636 double thresh=cr.getMean()+(cr.getMax()-cr.getMean())*.15;
639 cval = forCurrentX.getContactAt(c);
640 if (// cr.getMin() <= cval &&
643 av.getColumnSelection().addElement(c--);
651 while (c < forCurrentX.getContactHeight())
653 cval = forCurrentX.getContactAt(c);
654 if (// cr.getMin() <= cval &&
657 av.getColumnSelection().addElement(c++);
671 ap.getScalePanel().mousePressed(evt);
676 * Construct and display a context menu at the right-click position
681 void showPopupMenu(final int y, int x)
683 if (av.getColumnSelection() == null
684 || av.getColumnSelection().isEmpty())
689 JPopupMenu pop = new JPopupMenu(
690 MessageManager.getString("label.structure_type"));
693 * Just display the needed structure options
695 if (av.getAlignment().isNucleotide())
697 item = new JMenuItem(STEM);
698 item.addActionListener(this);
703 item = new JMenuItem(HELIX);
704 item.addActionListener(this);
706 item = new JMenuItem(SHEET);
707 item.addActionListener(this);
710 item = new JMenuItem(LABEL);
711 item.addActionListener(this);
713 item = new JMenuItem(COLOUR);
714 item.addActionListener(this);
716 item = new JMenuItem(REMOVE);
717 item.addActionListener(this);
719 pop.show(this, x, y);
723 * Action on mouse up is to clear mouse drag data and call mouseReleased on
724 * ScalePanel, to deal with defining the selection group (if any) defined by
730 public void mouseReleased(MouseEvent evt)
732 if (dragMode == DragMode.MatrixSelect)
734 matrixSelectRange(evt);
741 mouseDragging = false;
742 if (dragMode == DragMode.Resize)
744 ap.adjustAnnotationHeight();
746 dragMode = DragMode.Undefined;
747 ap.getScalePanel().mouseReleased(evt);
750 * isPopupTrigger is set in mouseReleased on Windows
751 * (in mousePressed on Mac)
753 if (evt.isPopupTrigger() && activeRow != -1)
755 showPopupMenu(evt.getY(), evt.getX());
767 public void mouseEntered(MouseEvent evt)
769 this.mouseDragging = false;
770 ap.getScalePanel().mouseEntered(evt);
774 * On leaving the panel, calls ScalePanel.mouseExited to deal with scrolling
775 * with column selection on a mouse drag
780 public void mouseExited(MouseEvent evt)
782 ap.getScalePanel().mouseExited(evt);
786 * Action on starting or continuing a mouse drag. There are two possible
789 * <li>drag up or down on a graphed annotation increases or decreases the
790 * height of the graph</li>
791 * <li>dragging left or right selects the columns dragged across</li>
793 * A drag on a graph annotation is treated as column selection if it starts
794 * with more horizontal than vertical movement, and as resize if it starts
795 * with more vertical than horizontal movement. Once started, the drag does
801 public void mouseDragged(MouseEvent evt)
804 * if dragMode is Undefined:
805 * - set to Select if dx > dy
806 * - set to Resize if dy > dx
807 * - do nothing if dx == dy
809 final int x = evt.getX();
810 final int y = evt.getY();
811 if (dragMode == DragMode.Undefined)
813 int dx = Math.abs(x - mouseDragLastX);
814 int dy = Math.abs(y - mouseDragLastY);
815 if (graphStretch == -1 || dx > dy)
818 * mostly horizontal drag, or not a graph annotation
820 dragMode = DragMode.Select;
825 * mostly vertical drag
827 dragMode = DragMode.Resize;
828 notJustOne = evt.isShiftDown();
831 * but could also be a matrix drag
833 if ((evt.isAltDown() || evt.isAltGraphDown()) && (av.getAlignment()
834 .getAlignmentAnnotation()[graphStretch].graph == AlignmentAnnotation.CONTACT_MAP))
837 * dragging in a matrix
839 dragMode = DragMode.MatrixSelect;
840 firstDragX = mouseDragLastX;
841 firstDragY = mouseDragLastY;
846 if (dragMode == DragMode.Undefined)
850 * drag is diagonal - defer deciding whether to
851 * treat as up/down or left/right
858 if (dragMode == DragMode.Resize)
861 * resize graph annotation if mouse was dragged up or down
863 int deltaY = mouseDragLastY - evt.getY();
866 AlignmentAnnotation graphAnnotation = av.getAlignment()
867 .getAlignmentAnnotation()[graphStretch];
868 int newHeight = Math.max(0, graphAnnotation.graphHeight + deltaY);
871 for (AlignmentAnnotation similar : av.getAlignment()
872 .findAnnotations(null, graphAnnotation.getCalcId(),
873 graphAnnotation.label))
875 similar.graphHeight = newHeight;
881 graphAnnotation.graphHeight = newHeight;
884 ap.paintAlignment(false, false);
887 else if (dragMode == DragMode.MatrixSelect)
890 * TODO draw a rubber band for range
894 ap.paintAlignment(false, false);
899 * for mouse drag left or right, delegate to
900 * ScalePanel to adjust the column selection
902 ap.getScalePanel().mouseDragged(evt);
911 public void matrixSelectRange(MouseEvent evt)
914 * get geometry of drag
916 int fromY = Math.min(firstDragY, evt.getY());
917 int toY = Math.max(firstDragY, evt.getY());
918 int fromX = Math.min(firstDragX, evt.getX());
919 int toX = Math.max(firstDragX, evt.getX());
921 int deltaY = toY - fromY;
922 int deltaX = toX - fromX;
924 int[] rowIndex = getRowIndexAndOffset(fromY,
925 av.getAlignment().getAlignmentAnnotation());
926 int[] toRowIndex = getRowIndexAndOffset(toY,
927 av.getAlignment().getAlignmentAnnotation());
929 if (rowIndex == null || toRowIndex == null)
931 System.out.println("Drag out of range. needs to be clipped");
934 if (rowIndex[0] != toRowIndex[0])
936 System.out.println("Drag went to another row. needs to be clipped");
939 // rectangular selection on matrix style annotation
940 AlignmentAnnotation cma = av.getAlignment()
941 .getAlignmentAnnotation()[rowIndex[0]];
943 int lastX = getColumnForXPos(fromX);
944 int currentX = getColumnForXPos(toX);
945 int fromXc = Math.min(lastX, currentX);
946 int toXc = Math.max(lastX, currentX);
947 ContactListI forFromX = av.getContactList(cma, fromXc);
948 ContactListI forToX = av.getContactList(cma, toXc);
950 if (forFromX != null && forToX != null)
952 ContactGeometry lastXcgeom = new ContactGeometry(forFromX,
954 ContactGeometry.contactInterval lastXci = lastXcgeom
955 .mapFor(rowIndex[1], rowIndex[1] - deltaY);
957 ContactGeometry cXcgeom = new ContactGeometry(forToX,
959 ContactGeometry.contactInterval cXci = cXcgeom.mapFor(rowIndex[1],
960 rowIndex[1] - deltaY);
962 // mark rectangular region formed by drag
963 System.err.println("Matrix Selection from last(" + fromXc + ",["
964 + lastXci.cStart + "," + lastXci.cEnd + "]) to cur(" + toXc
965 + ",[" + cXci.cStart + "," + cXci.cEnd + "])");
967 fr = Math.min(lastXci.cStart, lastXci.cEnd);
968 to = Math.max(lastXci.cStart, lastXci.cEnd);
969 System.err.println("Marking " + fr + " to " + to);
970 for (int c = fr; c <= to; c++)
972 if (cma.sequenceRef != null)
974 int col = cma.sequenceRef.findIndex(c);
975 av.getColumnSelection().addElement(col);
979 av.getColumnSelection().addElement(c);
982 fr = Math.min(cXci.cStart, cXci.cEnd);
983 to = Math.max(cXci.cStart, cXci.cEnd);
984 System.err.println("Marking " + fr + " to " + to);
985 for (int c = fr; c <= to; c++)
987 if (cma.sequenceRef != null)
989 int col = cma.sequenceRef.findIndex(c);
990 av.getColumnSelection().addElement(col);
994 av.getColumnSelection().addElement(c);
997 fr = Math.min(lastX, currentX);
998 to = Math.max(lastX, currentX);
1000 System.err.println("Marking " + fr + " to " + to);
1001 for (int c = fr; c <= to; c++)
1003 av.getColumnSelection().addElement(c);
1010 * Constructs the tooltip, and constructs and displays a status message, for
1011 * the current mouse position
1016 public void mouseMoved(MouseEvent evt)
1018 int yPos = evt.getY();
1019 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
1020 int rowAndOffset[] = getRowIndexAndOffset(yPos, aa);
1021 int row = rowAndOffset[0];
1025 this.setToolTipText(null);
1029 int column = getColumnForXPos(evt.getX());
1031 AlignmentAnnotation ann = aa[row];
1032 if (row > -1 && ann.annotations != null
1033 && column < ann.annotations.length)
1035 String toolTip = buildToolTip(ann, column, aa, rowAndOffset[1], av,
1037 setToolTipText(toolTip == null ? null
1038 : JvSwingUtils.wrapTooltip(true, toolTip));
1039 String msg = getStatusMessage(av.getAlignment(), column, ann,
1040 rowAndOffset[1], av);
1041 ap.alignFrame.setStatus(msg);
1045 this.setToolTipText(null);
1046 ap.alignFrame.setStatus(" ");
1050 private int getColumnForXPos(int x)
1052 int column = (x / av.getCharWidth()) + av.getRanges().getStartRes();
1053 column = Math.min(column, av.getRanges().getEndRes());
1055 if (av.hasHiddenColumns())
1057 column = av.getAlignment().getHiddenColumns()
1058 .visibleToAbsoluteColumn(column);
1064 * Answers the index in the annotations array of the visible annotation at the
1065 * given y position. This is done by adding the heights of visible annotations
1066 * until the y position has been exceeded. Answers -1 if no annotations are
1067 * visible, or the y position is below all annotations.
1073 static int getRowIndex(int yPos, AlignmentAnnotation[] aa)
1079 return getRowIndexAndOffset(yPos, aa)[0];
1082 static int[] getRowIndexAndOffset(int yPos, AlignmentAnnotation[] aa)
1084 int[] res = new int[2];
1092 int height = 0, lheight = 0;
1093 for (int i = 0; i < aa.length; i++)
1098 height += aa[i].height;
1105 res[1] = height - yPos;
1113 * Answers a tooltip for the annotation at the current mouse position, not
1114 * wrapped in <html> tags (apply if wanted). Answers null if there is no
1120 * @param rowAndOffset
1122 static String buildToolTip(AlignmentAnnotation ann, int column,
1123 AlignmentAnnotation[] anns, int rowAndOffset, AlignViewportI av,
1126 String tooltip = null;
1127 if (ann.graphGroup > -1)
1129 StringBuilder tip = new StringBuilder(32);
1130 boolean first = true;
1131 for (int i = 0; i < anns.length; i++)
1133 if (anns[i].graphGroup == ann.graphGroup
1134 && anns[i].annotations[column] != null)
1141 tip.append(anns[i].label);
1142 String description = anns[i].annotations[column].description;
1143 if (description != null && description.length() > 0)
1145 tip.append(" ").append(description);
1149 tooltip = first ? null : tip.toString();
1151 else if (column < ann.annotations.length
1152 && ann.annotations[column] != null)
1154 tooltip = ann.annotations[column].description;
1156 // TODO abstract tooltip generator so different implementations can be built
1157 if (ann.graph == AlignmentAnnotation.CONTACT_MAP)
1159 ContactListI clist = av.getContactList(ann, column);
1162 ContactGeometry cgeom = new ContactGeometry(clist, ann.graphHeight);
1163 ContactGeometry.contactInterval ci = cgeom.mapFor(rowAndOffset);
1164 ContactRange cr = clist.getRangeFor(ci.cStart, ci.cEnd);
1165 tooltip = "Contact from " + clist.getPosition() + ", [" + ci.cStart
1166 + " - " + ci.cEnd + "]" + "<br/>Mean:" + cr.getMean();
1167 int col = ann.sequenceRef.findPosition(column);
1168 ap.getStructureSelectionManager()
1169 .highlightPositionsOn(ann.sequenceRef, new int[][]
1170 { new int[] { col, col },
1172 { ci.cStart, ci.cEnd } }, null);
1179 * Constructs and returns the status bar message
1184 * @param rowAndOffset
1186 static String getStatusMessage(AlignmentI al, int column,
1187 AlignmentAnnotation ann, int rowAndOffset, AlignViewportI av)
1190 * show alignment column and annotation description if any
1192 StringBuilder text = new StringBuilder(32);
1193 text.append(MessageManager.getString("label.column")).append(" ")
1194 .append(column + 1);
1196 if (column < ann.annotations.length && ann.annotations[column] != null)
1198 String description = ann.annotations[column].description;
1199 if (description != null && description.trim().length() > 0)
1201 text.append(" ").append(description);
1206 * if the annotation is sequence-specific, show the sequence number
1207 * in the alignment, and (if not a gap) the residue and position
1209 SequenceI seqref = ann.sequenceRef;
1212 int seqIndex = al.findIndex(seqref);
1215 text.append(", ").append(MessageManager.getString("label.sequence"))
1216 .append(" ").append(seqIndex + 1);
1217 char residue = seqref.getCharAt(column);
1218 if (!Comparison.isGap(residue))
1222 if (al.isNucleotide())
1224 name = ResidueProperties.nucleotideName
1225 .get(String.valueOf(residue));
1226 text.append(" Nucleotide: ")
1227 .append(name != null ? name : residue);
1231 name = 'X' == residue ? "X"
1232 : ('*' == residue ? "STOP"
1233 : ResidueProperties.aa2Triplet
1234 .get(String.valueOf(residue)));
1235 text.append(" Residue: ").append(name != null ? name : residue);
1237 int residuePos = seqref.findPosition(column);
1238 text.append(" (").append(residuePos).append(")");
1243 return text.toString();
1253 public void mouseClicked(MouseEvent evt)
1255 // if (activeRow != -1)
1257 // AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
1258 // AlignmentAnnotation anot = aa[activeRow];
1262 // TODO mouseClicked-content and drawCursor are quite experimental!
1263 public void drawCursor(Graphics graphics, SequenceI seq, int res, int x1,
1266 int pady = av.getCharHeight() / 5;
1268 graphics.setColor(Color.black);
1269 graphics.fillRect(x1, y1, av.getCharWidth(), av.getCharHeight());
1271 if (av.validCharWidth)
1273 graphics.setColor(Color.white);
1275 char s = seq.getCharAt(res);
1277 charOffset = (av.getCharWidth() - fm.charWidth(s)) / 2;
1278 graphics.drawString(String.valueOf(s), charOffset + x1,
1279 (y1 + av.getCharHeight()) - pady);
1284 private volatile boolean imageFresh = false;
1286 private Rectangle visibleRect = new Rectangle(),
1287 clipBounds = new Rectangle();
1296 public void paintComponent(Graphics g)
1299 // BH: note that this method is generally recommended to
1300 // call super.paintComponent(g). Otherwise, the children of this
1301 // component will not be rendered. That is not needed here
1302 // because AnnotationPanel does not have any children. It is
1303 // just a JPanel contained in a JViewPort.
1305 computeVisibleRect(visibleRect);
1307 g.setColor(Color.white);
1308 g.fillRect(0, 0, visibleRect.width, visibleRect.height);
1312 // BH 2018 optimizing generation of new Rectangle().
1314 || (visibleRect.width != (clipBounds = g
1315 .getClipBounds(clipBounds)).width)
1316 || (visibleRect.height != clipBounds.height))
1319 g.drawImage(image, 0, 0, this);
1324 imgWidth = (av.getRanges().getEndRes() - av.getRanges().getStartRes()
1325 + 1) * av.getCharWidth();
1331 if (image == null || imgWidth != image.getWidth(this)
1332 || image.getHeight(this) != getHeight())
1334 boolean tried = false;
1336 while (image == null && !tried)
1340 image = new BufferedImage(imgWidth,
1341 ap.getAnnotationPanel().getHeight(),
1342 BufferedImage.TYPE_INT_RGB);
1344 } catch (IllegalArgumentException exc)
1347 "Serious issue with viewport geometry imgWidth requested was "
1350 } catch (OutOfMemoryError oom)
1355 } catch (Exception x)
1360 "Couldn't allocate memory to redraw screen. Please restart Jalview",
1366 gg = (Graphics2D) image.getGraphics();
1370 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
1371 RenderingHints.VALUE_ANTIALIAS_ON);
1374 gg.setFont(av.getFont());
1375 fm = gg.getFontMetrics();
1376 gg.setColor(Color.white);
1377 gg.fillRect(0, 0, imgWidth, image.getHeight());
1382 gg = (Graphics2D) image.getGraphics();
1386 drawComponent(gg, av.getRanges().getStartRes(),
1387 av.getRanges().getEndRes() + 1);
1390 g.drawImage(image, 0, 0, this);
1394 * set true to enable redraw timing debug output on stderr
1396 private final boolean debugRedraw = false;
1399 * non-Thread safe repaint
1402 * repaint with horizontal shift in alignment
1404 public void fastPaint(int horizontal)
1406 if ((horizontal == 0) || image == null
1407 || av.getAlignment().getAlignmentAnnotation() == null
1408 || av.getAlignment().getAlignmentAnnotation().length < 1
1409 || av.isCalcInProgress())
1415 int sr = av.getRanges().getStartRes();
1416 int er = av.getRanges().getEndRes() + 1;
1419 Graphics2D gg = (Graphics2D) image.getGraphics();
1421 if (imgWidth > Math.abs(horizontal * av.getCharWidth()))
1423 // scroll is less than imgWidth away so can re-use buffered graphics
1424 gg.copyArea(0, 0, imgWidth, getHeight(),
1425 -horizontal * av.getCharWidth(), 0);
1427 if (horizontal > 0) // scrollbar pulled right, image to the left
1429 transX = (er - sr - horizontal) * av.getCharWidth();
1430 sr = er - horizontal;
1432 else if (horizontal < 0)
1434 er = sr - horizontal;
1437 gg.translate(transX, 0);
1439 drawComponent(gg, sr, er);
1441 gg.translate(-transX, 0);
1447 // Call repaint on alignment panel so that repaints from other alignment
1448 // panel components can be aggregated. Otherwise performance of the overview
1449 // window and others may be adversely affected.
1450 av.getAlignPanel().repaint();
1453 private volatile boolean lastImageGood = false;
1465 public void drawComponent(Graphics g, int startRes, int endRes)
1467 BufferedImage oldFaded = fadedImage;
1468 if (av.isCalcInProgress())
1472 lastImageGood = false;
1475 // We'll keep a record of the old image,
1476 // and draw a faded image until the calculation
1479 && (fadedImage == null || fadedImage.getWidth() != imgWidth
1480 || fadedImage.getHeight() != image.getHeight()))
1482 // System.err.println("redraw faded image ("+(fadedImage==null ?
1483 // "null image" : "") + " lastGood="+lastImageGood+")");
1484 fadedImage = new BufferedImage(imgWidth, image.getHeight(),
1485 BufferedImage.TYPE_INT_RGB);
1487 Graphics2D fadedG = (Graphics2D) fadedImage.getGraphics();
1489 fadedG.setColor(Color.white);
1490 fadedG.fillRect(0, 0, imgWidth, image.getHeight());
1492 fadedG.setComposite(
1493 AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .3f));
1494 fadedG.drawImage(image, 0, 0, this);
1497 // make sure we don't overwrite the last good faded image until all
1498 // calculations have finished
1499 lastImageGood = false;
1504 if (fadedImage != null)
1506 oldFaded = fadedImage;
1511 g.setColor(Color.white);
1512 g.fillRect(0, 0, (endRes - startRes) * av.getCharWidth(), getHeight());
1514 g.setFont(av.getFont());
1517 fm = g.getFontMetrics();
1520 if ((av.getAlignment().getAlignmentAnnotation() == null)
1521 || (av.getAlignment().getAlignmentAnnotation().length < 1))
1523 g.setColor(Color.white);
1524 g.fillRect(0, 0, getWidth(), getHeight());
1525 g.setColor(Color.black);
1526 if (av.validCharWidth)
1528 g.drawString(MessageManager
1529 .getString("label.alignment_has_no_annotations"), 20, 15);
1534 lastImageGood = renderer.drawComponent(this, av, g, activeRow, startRes,
1536 if (!lastImageGood && fadedImage == null)
1538 fadedImage = oldFaded;
1540 if (dragMode == DragMode.MatrixSelect)
1542 g.setColor(Color.yellow);
1543 g.drawRect(Math.min(firstDragX, mouseDragLastX),
1544 Math.min(firstDragY, mouseDragLastY),
1545 Math.max(firstDragX, mouseDragLastX)
1546 - Math.min(firstDragX, mouseDragLastX),
1547 Math.max(firstDragY, mouseDragLastY)
1548 - Math.min(firstDragY, mouseDragLastY));
1554 public FontMetrics getFontMetrics()
1560 public Image getFadedImage()
1566 public int getFadedImageWidth()
1571 private int[] bounds = new int[2];
1574 public int[] getVisibleVRange()
1576 if (ap != null && ap.getAlabels() != null)
1578 int sOffset = -ap.getAlabels().getScrollOffset();
1579 int visHeight = sOffset + ap.annotationSpaceFillerHolder.getHeight();
1580 bounds[0] = sOffset;
1581 bounds[1] = visHeight;
1591 * Try to ensure any references held are nulled
1593 public void dispose()
1603 * I created the renderer so I will dispose of it
1605 if (renderer != null)
1612 public void propertyChange(PropertyChangeEvent evt)
1614 // Respond to viewport range changes (e.g. alignment panel was scrolled)
1615 // Both scrolling and resizing change viewport ranges: scrolling changes
1616 // both start and end points, but resize only changes end values.
1617 // Here we only want to fastpaint on a scroll, with resize using a normal
1618 // paint, so scroll events are identified as changes to the horizontal or
1619 // vertical start value.
1620 if (evt.getPropertyName().equals(ViewportRanges.STARTRES))
1622 fastPaint((int) evt.getNewValue() - (int) evt.getOldValue());
1624 else if (evt.getPropertyName().equals(ViewportRanges.STARTRESANDSEQ))
1626 fastPaint(((int[]) evt.getNewValue())[0]
1627 - ((int[]) evt.getOldValue())[0]);
1629 else if (evt.getPropertyName().equals(ViewportRanges.MOVE_VIEWPORT))
1636 * computes the visible height of the annotation panel
1638 * @param adjustPanelHeight
1639 * - when false, just adjust existing height according to other
1641 * @param annotationHeight
1642 * @return height to use for the ScrollerPreferredVisibleSize
1644 public int adjustForAlignFrame(boolean adjustPanelHeight,
1645 int annotationHeight)
1648 * Estimate available height in the AlignFrame for alignment +
1649 * annotations. Deduct an estimate for title bar, menu bar, scale panel,
1650 * hscroll, status bar, insets.
1652 int stuff = (ap.getViewName() != null ? 30 : 0)
1653 + (Platform.isAMacAndNotJS() ? 120 : 140);
1654 int availableHeight = ap.alignFrame.getHeight() - stuff;
1655 int rowHeight = av.getCharHeight();
1657 if (adjustPanelHeight)
1659 int alignmentHeight = rowHeight * av.getAlignment().getHeight();
1662 * If not enough vertical space, maximize annotation height while keeping
1663 * at least two rows of alignment visible
1665 if (annotationHeight + alignmentHeight > availableHeight)
1667 annotationHeight = Math.min(annotationHeight,
1668 availableHeight - 2 * rowHeight);
1673 // maintain same window layout whilst updating sliders
1674 annotationHeight = Math.min(ap.annotationScroller.getSize().height,
1675 availableHeight - 2 * rowHeight);
1677 return annotationHeight;