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;
75 * AnnotationPanel displays visible portion of annotation rows below unwrapped
81 public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
82 MouseListener, MouseWheelListener, MouseMotionListener,
83 ActionListener, AdjustmentListener, Scrollable, ViewportListenerI
87 Select, Resize, Undefined, MatrixSelect
90 String HELIX = MessageManager.getString("label.helix");
92 String SHEET = MessageManager.getString("label.sheet");
95 * For RNA secondary structure "stems" aka helices
97 String STEM = MessageManager.getString("label.rna_helix");
99 String LABEL = MessageManager.getString("label.label");
101 String REMOVE = MessageManager.getString("label.remove_annotation");
103 String COLOUR = MessageManager.getString("action.colour");
105 public final Color HELIX_COLOUR = Color.red.darker();
107 public final Color SHEET_COLOUR = Color.green.darker().darker();
109 public final Color STEM_COLOUR = Color.blue.darker();
112 public AlignViewport av;
116 public int activeRow = -1;
118 public BufferedImage image;
120 public volatile BufferedImage fadedImage;
122 // private Graphics2D gg;
124 public FontMetrics fm;
126 public int imgWidth = 0;
128 boolean fastPaint = false;
130 // Used For mouse Dragging and resizing graphs
131 int graphStretch = -1;
133 int mouseDragLastX = -1;
135 int mouseDragLastY = -1;
141 DragMode dragMode = DragMode.Undefined;
143 boolean mouseDragging = false;
145 // for editing cursor
150 public final AnnotationRenderer renderer;
152 private MouseWheelListener[] _mwl;
154 private boolean notJustOne;
157 * Creates a new AnnotationPanel object.
162 public AnnotationPanel(AlignmentPanel ap)
164 ToolTipManager.sharedInstance().registerComponent(this);
165 ToolTipManager.sharedInstance().setInitialDelay(0);
166 ToolTipManager.sharedInstance().setDismissDelay(10000);
169 this.setLayout(null);
170 addMouseListener(this);
171 addMouseMotionListener(this);
172 ap.annotationScroller.getVerticalScrollBar()
173 .addAdjustmentListener(this);
174 // save any wheel listeners on the scroller, so we can propagate scroll
176 _mwl = ap.annotationScroller.getMouseWheelListeners();
177 // and then set our own listener to consume all mousewheel events
178 ap.annotationScroller.addMouseWheelListener(this);
179 renderer = new AnnotationRenderer();
181 av.getRanges().addPropertyChangeListener(this);
184 public AnnotationPanel(AlignViewport av)
187 renderer = new AnnotationRenderer();
191 public void mouseWheelMoved(MouseWheelEvent e)
196 double wheelRotation = e.getPreciseWheelRotation();
197 if (wheelRotation > 0)
199 av.getRanges().scrollRight(true);
201 else if (wheelRotation < 0)
203 av.getRanges().scrollRight(false);
208 // TODO: find the correct way to let the event bubble up to
209 // ap.annotationScroller
210 for (MouseWheelListener mwl : _mwl)
214 mwl.mouseWheelMoved(e);
225 public Dimension getPreferredScrollableViewportSize()
227 Dimension ps = getPreferredSize();
228 return new Dimension(ps.width, adjustForAlignFrame(false, ps.height));
232 public int getScrollableBlockIncrement(Rectangle visibleRect,
233 int orientation, int direction)
239 public boolean getScrollableTracksViewportHeight()
245 public boolean getScrollableTracksViewportWidth()
251 public int getScrollableUnitIncrement(Rectangle visibleRect,
252 int orientation, int direction)
261 * java.awt.event.AdjustmentListener#adjustmentValueChanged(java.awt.event
265 public void adjustmentValueChanged(AdjustmentEvent evt)
267 // update annotation label display
268 ap.getAlabels().setScrollOffset(-evt.getValue());
272 * Calculates the height of the annotation displayed in the annotation panel.
273 * Callers should normally call the ap.adjustAnnotationHeight method to ensure
274 * all annotation associated components are updated correctly.
277 public int adjustPanelHeight()
279 int height = av.calcPanelHeight();
280 this.setPreferredSize(new Dimension(1, height));
283 // revalidate only when the alignment panel is fully constructed
297 public void actionPerformed(ActionEvent evt)
299 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
304 Annotation[] anot = aa[activeRow].annotations;
306 if (anot.length < av.getColumnSelection().getMax())
308 Annotation[] temp = new Annotation[av.getColumnSelection().getMax()
310 System.arraycopy(anot, 0, temp, 0, anot.length);
312 aa[activeRow].annotations = anot;
315 String action = evt.getActionCommand();
316 if (action.equals(REMOVE))
318 for (int index : av.getColumnSelection().getSelected())
320 if (av.getAlignment().getHiddenColumns().isVisible(index))
326 else if (action.equals(LABEL))
328 String exMesg = collectAnnotVals(anot, LABEL);
329 String label = JvOptionPane.showInputDialog(
330 MessageManager.getString("label.enter_label"), exMesg);
337 if ((label.length() > 0) && !aa[activeRow].hasText)
339 aa[activeRow].hasText = true;
342 for (int index : av.getColumnSelection().getSelected())
344 if (!av.getAlignment().getHiddenColumns().isVisible(index))
349 if (anot[index] == null)
351 anot[index] = new Annotation(label, "", ' ', 0);
355 anot[index].displayCharacter = label;
359 else if (action.equals(COLOUR))
361 final Annotation[] fAnot = anot;
362 String title = MessageManager
363 .getString("label.select_foreground_colour");
364 ColourChooserListener listener = new ColourChooserListener()
367 public void colourSelected(Color c)
369 HiddenColumns hiddenColumns = av.getAlignment()
371 for (int index : av.getColumnSelection().getSelected())
373 if (hiddenColumns.isVisible(index))
375 if (fAnot[index] == null)
377 fAnot[index] = new Annotation("", "", ' ', 0);
379 fAnot[index].colour = c;
384 JalviewColourChooser.showColourChooser(this, title, Color.black,
388 // HELIX, SHEET or STEM
391 String symbol = "\u03B1"; // alpha
393 if (action.equals(HELIX))
397 else if (action.equals(SHEET))
400 symbol = "\u03B2"; // beta
403 // Added by LML to color stems
404 else if (action.equals(STEM))
407 int column = av.getColumnSelection().getSelectedRanges().get(0)[0];
408 symbol = aa[activeRow].getDefaultRnaHelixSymbol(column);
411 if (!aa[activeRow].hasIcons)
413 aa[activeRow].hasIcons = true;
416 String label = JvOptionPane.showInputDialog(MessageManager
417 .getString("label.enter_label_for_the_structure"), symbol);
424 if ((label.length() > 0) && !aa[activeRow].hasText)
426 aa[activeRow].hasText = true;
427 if (action.equals(STEM))
429 aa[activeRow].showAllColLabels = true;
432 for (int index : av.getColumnSelection().getSelected())
434 if (!av.getAlignment().getHiddenColumns().isVisible(index))
439 if (anot[index] == null)
441 anot[index] = new Annotation(label, "", type, 0);
444 anot[index].secondaryStructure = type != 'S' ? type
445 : label.length() == 0 ? ' ' : label.charAt(0);
446 anot[index].displayCharacter = label;
451 av.getAlignment().validateAnnotation(aa[activeRow]);
452 ap.alignmentChanged();
453 ap.alignFrame.setMenusForViewport();
461 * Returns any existing annotation concatenated as a string. For each
462 * annotation, takes the description, if any, else the secondary structure
463 * character (if type is HELIX, SHEET or STEM), else the display character (if
470 private String collectAnnotVals(Annotation[] anots, String type)
472 // TODO is this method wanted? why? 'last' is not used
474 StringBuilder collatedInput = new StringBuilder(64);
476 ColumnSelection viscols = av.getColumnSelection();
477 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
480 * the selection list (read-only view) is in selection order, not
481 * column order; make a copy so we can sort it
483 List<Integer> selected = new ArrayList<>(viscols.getSelected());
484 Collections.sort(selected);
485 for (int index : selected)
487 // always check for current display state - just in case
488 if (!hidden.isVisible(index))
492 String tlabel = null;
493 if (anots[index] != null)
494 { // LML added stem code
495 if (type.equals(HELIX) || type.equals(SHEET) || type.equals(STEM)
496 || type.equals(LABEL))
498 tlabel = anots[index].description;
499 if (tlabel == null || tlabel.length() < 1)
501 if (type.equals(HELIX) || type.equals(SHEET)
502 || type.equals(STEM))
504 tlabel = "" + anots[index].secondaryStructure;
508 tlabel = "" + anots[index].displayCharacter;
512 if (tlabel != null && !tlabel.equals(last))
514 if (last.length() > 0)
516 collatedInput.append(" ");
518 collatedInput.append(tlabel);
522 return collatedInput.toString();
526 * Action on right mouse pressed on Mac is to show a pop-up menu for the
527 * annotation. Action on left mouse pressed is to find which annotation is
528 * pressed and mark the start of a column selection or graph resize operation.
533 public void mousePressed(MouseEvent evt)
536 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
541 mouseDragLastX = evt.getX();
542 mouseDragLastY = evt.getY();
545 * add visible annotation heights until we reach the y
546 * position, to find which annotation it is in
551 // todo could reuse getRowIndexAndOffset ?
552 final int y = evt.getY();
554 for (int i = 0; i < aa.length; i++)
558 height += aa[i].height;
567 else if (aa[i].graph != 0)
570 * we have clicked on a resizable graph annotation
573 yOffset = height - y;
580 * isPopupTrigger fires in mousePressed on Mac,
581 * not until mouseRelease on Windows
583 if (evt.isPopupTrigger() && activeRow != -1)
585 showPopupMenu(y, evt.getX());
589 if (graphStretch != -1)
592 if (aa[graphStretch].graph == AlignmentAnnotation.CONTACT_MAP)
594 if (evt.isAltDown() || evt.isAltGraphDown())
596 dragMode = DragMode.MatrixSelect;
597 firstDragX = mouseDragLastX;
598 firstDragY = mouseDragLastY;
602 GraphLine thr = aa[graphStretch].getThreshold();
603 int currentX = getColumnForXPos(evt.getX());
604 ContactListI forCurrentX = av.getContactList(aa[graphStretch],
606 if (forCurrentX != null)
608 ContactGeometry cXcgeom = new ContactGeometry(forCurrentX,
609 aa[graphStretch].graphHeight);
610 ContactGeometry.contactInterval cXci = cXcgeom.mapFor(yOffset,
613 fr = Math.min(cXci.cStart, cXci.cEnd);
614 to = Math.max(cXci.cStart, cXci.cEnd);
615 // select corresponding range in segment under mouse
617 for (int c = fr; c <= to; c++)
619 av.getColumnSelection().addElement(c);
621 av.getColumnSelection().addElement(currentX);
623 // and also select everything lower than the max range adjacent
627 ContactRange cr = forCurrentX.getRangeFor(fr, to);
631 cval = forCurrentX.getContactAt(c);
632 if (// cr.getMin() <= cval &&
635 av.getColumnSelection().addElement(c--);
643 while (c < forCurrentX.getContactHeight())
645 cval = forCurrentX.getContactAt(c);
646 if (// cr.getMin() <= cval &&
649 av.getColumnSelection().addElement(c++);
663 ap.getScalePanel().mousePressed(evt);
668 * Construct and display a context menu at the right-click position
673 void showPopupMenu(final int y, int x)
675 if (av.getColumnSelection() == null
676 || av.getColumnSelection().isEmpty())
681 JPopupMenu pop = new JPopupMenu(
682 MessageManager.getString("label.structure_type"));
685 * Just display the needed structure options
687 if (av.getAlignment().isNucleotide())
689 item = new JMenuItem(STEM);
690 item.addActionListener(this);
695 item = new JMenuItem(HELIX);
696 item.addActionListener(this);
698 item = new JMenuItem(SHEET);
699 item.addActionListener(this);
702 item = new JMenuItem(LABEL);
703 item.addActionListener(this);
705 item = new JMenuItem(COLOUR);
706 item.addActionListener(this);
708 item = new JMenuItem(REMOVE);
709 item.addActionListener(this);
711 pop.show(this, x, y);
715 * Action on mouse up is to clear mouse drag data and call mouseReleased on
716 * ScalePanel, to deal with defining the selection group (if any) defined by
722 public void mouseReleased(MouseEvent evt)
724 if (dragMode == DragMode.MatrixSelect)
726 matrixSelectRange(evt);
733 mouseDragging = false;
734 if (dragMode == DragMode.Resize)
736 ap.adjustAnnotationHeight();
738 dragMode = DragMode.Undefined;
739 ap.getScalePanel().mouseReleased(evt);
742 * isPopupTrigger is set in mouseReleased on Windows
743 * (in mousePressed on Mac)
745 if (evt.isPopupTrigger() && activeRow != -1)
747 showPopupMenu(evt.getY(), evt.getX());
759 public void mouseEntered(MouseEvent evt)
761 this.mouseDragging = false;
762 ap.getScalePanel().mouseEntered(evt);
766 * On leaving the panel, calls ScalePanel.mouseExited to deal with scrolling
767 * with column selection on a mouse drag
772 public void mouseExited(MouseEvent evt)
774 ap.getScalePanel().mouseExited(evt);
778 * Action on starting or continuing a mouse drag. There are two possible
781 * <li>drag up or down on a graphed annotation increases or decreases the
782 * height of the graph</li>
783 * <li>dragging left or right selects the columns dragged across</li>
785 * A drag on a graph annotation is treated as column selection if it starts
786 * with more horizontal than vertical movement, and as resize if it starts
787 * with more vertical than horizontal movement. Once started, the drag does
793 public void mouseDragged(MouseEvent evt)
796 * if dragMode is Undefined:
797 * - set to Select if dx > dy
798 * - set to Resize if dy > dx
799 * - do nothing if dx == dy
801 final int x = evt.getX();
802 final int y = evt.getY();
803 if (dragMode == DragMode.Undefined)
805 int dx = Math.abs(x - mouseDragLastX);
806 int dy = Math.abs(y - mouseDragLastY);
807 if (graphStretch == -1 || dx > dy)
810 * mostly horizontal drag, or not a graph annotation
812 dragMode = DragMode.Select;
817 * mostly vertical drag
819 dragMode = DragMode.Resize;
820 notJustOne = evt.isShiftDown();
823 * but could also be a matrix drag
825 if ((evt.isAltDown() || evt.isAltGraphDown()) && (av.getAlignment()
826 .getAlignmentAnnotation()[graphStretch].graph == AlignmentAnnotation.CONTACT_MAP))
829 * dragging in a matrix
831 dragMode = DragMode.MatrixSelect;
832 firstDragX = mouseDragLastX;
833 firstDragY = mouseDragLastY;
838 if (dragMode == DragMode.Undefined)
842 * drag is diagonal - defer deciding whether to
843 * treat as up/down or left/right
850 if (dragMode == DragMode.Resize)
853 * resize graph annotation if mouse was dragged up or down
855 int deltaY = mouseDragLastY - evt.getY();
858 AlignmentAnnotation graphAnnotation = av.getAlignment()
859 .getAlignmentAnnotation()[graphStretch];
860 int newHeight = Math.max(0, graphAnnotation.graphHeight + deltaY);
863 for (AlignmentAnnotation similar : av.getAlignment()
864 .findAnnotations(null, graphAnnotation.getCalcId(),
865 graphAnnotation.label))
867 similar.graphHeight = newHeight;
873 graphAnnotation.graphHeight = newHeight;
876 ap.paintAlignment(false, false);
879 else if (dragMode == DragMode.MatrixSelect)
882 * TODO draw a rubber band for range
886 ap.paintAlignment(false, false);
891 * for mouse drag left or right, delegate to
892 * ScalePanel to adjust the column selection
894 ap.getScalePanel().mouseDragged(evt);
903 public void matrixSelectRange(MouseEvent evt)
906 * get geometry of drag
908 int fromY = Math.min(firstDragY, evt.getY());
909 int toY = Math.max(firstDragY, evt.getY());
910 int fromX = Math.min(firstDragX, evt.getX());
911 int toX = Math.max(firstDragX, evt.getX());
913 int deltaY = toY - fromY;
914 int deltaX = toX - fromX;
916 int[] rowIndex = getRowIndexAndOffset(fromY,
917 av.getAlignment().getAlignmentAnnotation());
918 int[] toRowIndex = getRowIndexAndOffset(toY,
919 av.getAlignment().getAlignmentAnnotation());
921 if (rowIndex == null || toRowIndex == null)
923 System.out.println("Drag out of range. needs to be clipped");
926 if (rowIndex[0] != toRowIndex[0])
928 System.out.println("Drag went to another row. needs to be clipped");
931 // rectangular selection on matrix style annotation
932 AlignmentAnnotation cma = av.getAlignment()
933 .getAlignmentAnnotation()[rowIndex[0]];
935 int lastX = getColumnForXPos(fromX);
936 int currentX = getColumnForXPos(toX);
937 int fromXc = Math.min(lastX, currentX);
938 int toXc = Math.max(lastX, currentX);
939 ContactListI forFromX = av.getContactList(cma, fromXc);
940 ContactListI forToX = av.getContactList(cma, toXc);
942 if (forFromX != null && forToX != null)
944 ContactGeometry lastXcgeom = new ContactGeometry(forFromX,
946 ContactGeometry.contactInterval lastXci = lastXcgeom
947 .mapFor(rowIndex[1], rowIndex[1] - deltaY);
949 ContactGeometry cXcgeom = new ContactGeometry(forToX,
951 ContactGeometry.contactInterval cXci = cXcgeom.mapFor(rowIndex[1],
952 rowIndex[1] - deltaY);
954 // mark rectangular region formed by drag
955 System.err.println("Matrix Selection from last(" + fromXc + ",["
956 + lastXci.cStart + "," + lastXci.cEnd + "]) to cur(" + toXc
957 + ",[" + cXci.cStart + "," + cXci.cEnd + "])");
959 fr = Math.min(lastXci.cStart, lastXci.cEnd);
960 to = Math.max(lastXci.cStart, lastXci.cEnd);
961 System.err.println("Marking " + fr + " to " + to);
962 for (int c = fr; c <= to; c++)
964 if (cma.sequenceRef != null)
966 int col = cma.sequenceRef.findIndex(c);
967 av.getColumnSelection().addElement(col);
971 av.getColumnSelection().addElement(c);
974 fr = Math.min(cXci.cStart, cXci.cEnd);
975 to = Math.max(cXci.cStart, cXci.cEnd);
976 System.err.println("Marking " + fr + " to " + to);
977 for (int c = fr; c <= to; c++)
979 if (cma.sequenceRef != null)
981 int col = cma.sequenceRef.findIndex(c);
982 av.getColumnSelection().addElement(col);
986 av.getColumnSelection().addElement(c);
989 fr = Math.min(lastX, currentX);
990 to = Math.max(lastX, currentX);
992 System.err.println("Marking " + fr + " to " + to);
993 for (int c = fr; c <= to; c++)
995 av.getColumnSelection().addElement(c);
1002 * Constructs the tooltip, and constructs and displays a status message, for
1003 * the current mouse position
1008 public void mouseMoved(MouseEvent evt)
1010 int yPos = evt.getY();
1011 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
1012 int rowAndOffset[] = getRowIndexAndOffset(yPos, aa);
1013 int row = rowAndOffset[0];
1017 this.setToolTipText(null);
1021 int column = getColumnForXPos(evt.getX());
1023 AlignmentAnnotation ann = aa[row];
1024 if (row > -1 && ann.annotations != null
1025 && column < ann.annotations.length)
1027 String toolTip = buildToolTip(ann, column, aa, rowAndOffset[1], av,
1029 setToolTipText(toolTip == null ? null
1030 : JvSwingUtils.wrapTooltip(true, toolTip));
1031 String msg = getStatusMessage(av.getAlignment(), column, ann,
1032 rowAndOffset[1], av);
1033 ap.alignFrame.setStatus(msg);
1037 this.setToolTipText(null);
1038 ap.alignFrame.setStatus(" ");
1042 private int getColumnForXPos(int x)
1044 int column = (x / av.getCharWidth()) + av.getRanges().getStartRes();
1045 column = Math.min(column, av.getRanges().getEndRes());
1047 if (av.hasHiddenColumns())
1049 column = av.getAlignment().getHiddenColumns()
1050 .visibleToAbsoluteColumn(column);
1056 * Answers the index in the annotations array of the visible annotation at the
1057 * given y position. This is done by adding the heights of visible annotations
1058 * until the y position has been exceeded. Answers -1 if no annotations are
1059 * visible, or the y position is below all annotations.
1065 static int getRowIndex(int yPos, AlignmentAnnotation[] aa)
1071 return getRowIndexAndOffset(yPos, aa)[0];
1074 static int[] getRowIndexAndOffset(int yPos, AlignmentAnnotation[] aa)
1076 int[] res = new int[2];
1084 int height = 0, lheight = 0;
1085 for (int i = 0; i < aa.length; i++)
1090 height += aa[i].height;
1097 res[1] = height - yPos;
1105 * Answers a tooltip for the annotation at the current mouse position, not
1106 * wrapped in <html> tags (apply if wanted). Answers null if there is no
1112 * @param rowAndOffset
1114 static String buildToolTip(AlignmentAnnotation ann, int column,
1115 AlignmentAnnotation[] anns, int rowAndOffset, AlignViewportI av,
1118 String tooltip = null;
1119 if (ann.graphGroup > -1)
1121 StringBuilder tip = new StringBuilder(32);
1122 boolean first = true;
1123 for (int i = 0; i < anns.length; i++)
1125 if (anns[i].graphGroup == ann.graphGroup
1126 && anns[i].annotations[column] != null)
1133 tip.append(anns[i].label);
1134 String description = anns[i].annotations[column].description;
1135 if (description != null && description.length() > 0)
1137 tip.append(" ").append(description);
1141 tooltip = first ? null : tip.toString();
1143 else if (column < ann.annotations.length
1144 && ann.annotations[column] != null)
1146 tooltip = ann.annotations[column].description;
1148 // TODO abstract tooltip generator so different implementations can be built
1149 if (ann.graph == AlignmentAnnotation.CONTACT_MAP)
1151 ContactListI clist = av.getContactList(ann, column);
1154 ContactGeometry cgeom = new ContactGeometry(clist, ann.graphHeight);
1155 ContactGeometry.contactInterval ci = cgeom.mapFor(rowAndOffset);
1156 ContactRange cr = clist.getRangeFor(ci.cStart, ci.cEnd);
1157 tooltip = "Contact from " + clist.getPosition() + ", [" + ci.cStart
1158 + " - " + ci.cEnd + "]" + "<br/>Mean:" + cr.getMean();
1159 int col = ann.sequenceRef.findPosition(column);
1160 ap.getStructureSelectionManager()
1161 .highlightPositionsOn(ann.sequenceRef, new int[][]
1162 { new int[] { col, col },
1164 { ci.cStart, ci.cEnd } }, null);
1171 * Constructs and returns the status bar message
1176 * @param rowAndOffset
1178 static String getStatusMessage(AlignmentI al, int column,
1179 AlignmentAnnotation ann, int rowAndOffset, AlignViewportI av)
1182 * show alignment column and annotation description if any
1184 StringBuilder text = new StringBuilder(32);
1185 text.append(MessageManager.getString("label.column")).append(" ")
1186 .append(column + 1);
1188 if (column < ann.annotations.length && ann.annotations[column] != null)
1190 String description = ann.annotations[column].description;
1191 if (description != null && description.trim().length() > 0)
1193 text.append(" ").append(description);
1198 * if the annotation is sequence-specific, show the sequence number
1199 * in the alignment, and (if not a gap) the residue and position
1201 SequenceI seqref = ann.sequenceRef;
1204 int seqIndex = al.findIndex(seqref);
1207 text.append(", ").append(MessageManager.getString("label.sequence"))
1208 .append(" ").append(seqIndex + 1);
1209 char residue = seqref.getCharAt(column);
1210 if (!Comparison.isGap(residue))
1214 if (al.isNucleotide())
1216 name = ResidueProperties.nucleotideName
1217 .get(String.valueOf(residue));
1218 text.append(" Nucleotide: ")
1219 .append(name != null ? name : residue);
1223 name = 'X' == residue ? "X"
1224 : ('*' == residue ? "STOP"
1225 : ResidueProperties.aa2Triplet
1226 .get(String.valueOf(residue)));
1227 text.append(" Residue: ").append(name != null ? name : residue);
1229 int residuePos = seqref.findPosition(column);
1230 text.append(" (").append(residuePos).append(")");
1235 return text.toString();
1245 public void mouseClicked(MouseEvent evt)
1247 // if (activeRow != -1)
1249 // AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
1250 // AlignmentAnnotation anot = aa[activeRow];
1254 // TODO mouseClicked-content and drawCursor are quite experimental!
1255 public void drawCursor(Graphics graphics, SequenceI seq, int res, int x1,
1258 int pady = av.getCharHeight() / 5;
1260 graphics.setColor(Color.black);
1261 graphics.fillRect(x1, y1, av.getCharWidth(), av.getCharHeight());
1263 if (av.validCharWidth)
1265 graphics.setColor(Color.white);
1267 char s = seq.getCharAt(res);
1269 charOffset = (av.getCharWidth() - fm.charWidth(s)) / 2;
1270 graphics.drawString(String.valueOf(s), charOffset + x1,
1271 (y1 + av.getCharHeight()) - pady);
1276 private volatile boolean imageFresh = false;
1278 private Rectangle visibleRect = new Rectangle(),
1279 clipBounds = new Rectangle();
1288 public void paintComponent(Graphics g)
1291 // BH: note that this method is generally recommended to
1292 // call super.paintComponent(g). Otherwise, the children of this
1293 // component will not be rendered. That is not needed here
1294 // because AnnotationPanel does not have any children. It is
1295 // just a JPanel contained in a JViewPort.
1297 computeVisibleRect(visibleRect);
1299 g.setColor(Color.white);
1300 g.fillRect(0, 0, visibleRect.width, visibleRect.height);
1304 // BH 2018 optimizing generation of new Rectangle().
1306 || (visibleRect.width != (clipBounds = g
1307 .getClipBounds(clipBounds)).width)
1308 || (visibleRect.height != clipBounds.height))
1311 g.drawImage(image, 0, 0, this);
1316 imgWidth = (av.getRanges().getEndRes() - av.getRanges().getStartRes()
1317 + 1) * av.getCharWidth();
1323 if (image == null || imgWidth != image.getWidth(this)
1324 || image.getHeight(this) != getHeight())
1326 boolean tried = false;
1328 while (image == null && !tried)
1332 image = new BufferedImage(imgWidth,
1333 ap.getAnnotationPanel().getHeight(),
1334 BufferedImage.TYPE_INT_RGB);
1336 } catch (IllegalArgumentException exc)
1339 "Serious issue with viewport geometry imgWidth requested was "
1342 } catch (OutOfMemoryError oom)
1347 } catch (Exception x)
1352 "Couldn't allocate memory to redraw screen. Please restart Jalview",
1358 gg = (Graphics2D) image.getGraphics();
1362 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
1363 RenderingHints.VALUE_ANTIALIAS_ON);
1366 gg.setFont(av.getFont());
1367 fm = gg.getFontMetrics();
1368 gg.setColor(Color.white);
1369 gg.fillRect(0, 0, imgWidth, image.getHeight());
1374 gg = (Graphics2D) image.getGraphics();
1378 drawComponent(gg, av.getRanges().getStartRes(),
1379 av.getRanges().getEndRes() + 1);
1382 g.drawImage(image, 0, 0, this);
1386 * set true to enable redraw timing debug output on stderr
1388 private final boolean debugRedraw = false;
1391 * non-Thread safe repaint
1394 * repaint with horizontal shift in alignment
1396 public void fastPaint(int horizontal)
1398 if ((horizontal == 0) || image == null
1399 || av.getAlignment().getAlignmentAnnotation() == null
1400 || av.getAlignment().getAlignmentAnnotation().length < 1
1401 || av.isCalcInProgress())
1407 int sr = av.getRanges().getStartRes();
1408 int er = av.getRanges().getEndRes() + 1;
1411 Graphics2D gg = (Graphics2D) image.getGraphics();
1413 if (imgWidth > Math.abs(horizontal * av.getCharWidth()))
1415 // scroll is less than imgWidth away so can re-use buffered graphics
1416 gg.copyArea(0, 0, imgWidth, getHeight(),
1417 -horizontal * av.getCharWidth(), 0);
1419 if (horizontal > 0) // scrollbar pulled right, image to the left
1421 transX = (er - sr - horizontal) * av.getCharWidth();
1422 sr = er - horizontal;
1424 else if (horizontal < 0)
1426 er = sr - horizontal;
1429 gg.translate(transX, 0);
1431 drawComponent(gg, sr, er);
1433 gg.translate(-transX, 0);
1439 // Call repaint on alignment panel so that repaints from other alignment
1440 // panel components can be aggregated. Otherwise performance of the overview
1441 // window and others may be adversely affected.
1442 av.getAlignPanel().repaint();
1445 private volatile boolean lastImageGood = false;
1457 public void drawComponent(Graphics g, int startRes, int endRes)
1459 BufferedImage oldFaded = fadedImage;
1460 if (av.isCalcInProgress())
1464 lastImageGood = false;
1467 // We'll keep a record of the old image,
1468 // and draw a faded image until the calculation
1471 && (fadedImage == null || fadedImage.getWidth() != imgWidth
1472 || fadedImage.getHeight() != image.getHeight()))
1474 // System.err.println("redraw faded image ("+(fadedImage==null ?
1475 // "null image" : "") + " lastGood="+lastImageGood+")");
1476 fadedImage = new BufferedImage(imgWidth, image.getHeight(),
1477 BufferedImage.TYPE_INT_RGB);
1479 Graphics2D fadedG = (Graphics2D) fadedImage.getGraphics();
1481 fadedG.setColor(Color.white);
1482 fadedG.fillRect(0, 0, imgWidth, image.getHeight());
1484 fadedG.setComposite(
1485 AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .3f));
1486 fadedG.drawImage(image, 0, 0, this);
1489 // make sure we don't overwrite the last good faded image until all
1490 // calculations have finished
1491 lastImageGood = false;
1496 if (fadedImage != null)
1498 oldFaded = fadedImage;
1503 g.setColor(Color.white);
1504 g.fillRect(0, 0, (endRes - startRes) * av.getCharWidth(), getHeight());
1506 g.setFont(av.getFont());
1509 fm = g.getFontMetrics();
1512 if ((av.getAlignment().getAlignmentAnnotation() == null)
1513 || (av.getAlignment().getAlignmentAnnotation().length < 1))
1515 g.setColor(Color.white);
1516 g.fillRect(0, 0, getWidth(), getHeight());
1517 g.setColor(Color.black);
1518 if (av.validCharWidth)
1520 g.drawString(MessageManager
1521 .getString("label.alignment_has_no_annotations"), 20, 15);
1526 lastImageGood = renderer.drawComponent(this, av, g, activeRow, startRes,
1528 if (!lastImageGood && fadedImage == null)
1530 fadedImage = oldFaded;
1532 if (dragMode == DragMode.MatrixSelect)
1534 g.setColor(Color.yellow);
1535 g.drawRect(Math.min(firstDragX, mouseDragLastX),
1536 Math.min(firstDragY, mouseDragLastY),
1537 Math.max(firstDragX, mouseDragLastX)
1538 - Math.min(firstDragX, mouseDragLastX),
1539 Math.max(firstDragY, mouseDragLastY)
1540 - Math.min(firstDragY, mouseDragLastY));
1546 public FontMetrics getFontMetrics()
1552 public Image getFadedImage()
1558 public int getFadedImageWidth()
1563 private int[] bounds = new int[2];
1566 public int[] getVisibleVRange()
1568 if (ap != null && ap.getAlabels() != null)
1570 int sOffset = -ap.getAlabels().getScrollOffset();
1571 int visHeight = sOffset + ap.annotationSpaceFillerHolder.getHeight();
1572 bounds[0] = sOffset;
1573 bounds[1] = visHeight;
1583 * Try to ensure any references held are nulled
1585 public void dispose()
1595 * I created the renderer so I will dispose of it
1597 if (renderer != null)
1604 public void propertyChange(PropertyChangeEvent evt)
1606 // Respond to viewport range changes (e.g. alignment panel was scrolled)
1607 // Both scrolling and resizing change viewport ranges: scrolling changes
1608 // both start and end points, but resize only changes end values.
1609 // Here we only want to fastpaint on a scroll, with resize using a normal
1610 // paint, so scroll events are identified as changes to the horizontal or
1611 // vertical start value.
1612 if (evt.getPropertyName().equals(ViewportRanges.STARTRES))
1614 fastPaint((int) evt.getNewValue() - (int) evt.getOldValue());
1616 else if (evt.getPropertyName().equals(ViewportRanges.STARTRESANDSEQ))
1618 fastPaint(((int[]) evt.getNewValue())[0]
1619 - ((int[]) evt.getOldValue())[0]);
1621 else if (evt.getPropertyName().equals(ViewportRanges.MOVE_VIEWPORT))
1628 * computes the visible height of the annotation panel
1630 * @param adjustPanelHeight
1631 * - when false, just adjust existing height according to other
1633 * @param annotationHeight
1634 * @return height to use for the ScrollerPreferredVisibleSize
1636 public int adjustForAlignFrame(boolean adjustPanelHeight,
1637 int annotationHeight)
1640 * Estimate available height in the AlignFrame for alignment +
1641 * annotations. Deduct an estimate for title bar, menu bar, scale panel,
1642 * hscroll, status bar, insets.
1644 int stuff = (ap.getViewName() != null ? 30 : 0)
1645 + (Platform.isAMacAndNotJS() ? 120 : 140);
1646 int availableHeight = ap.alignFrame.getHeight() - stuff;
1647 int rowHeight = av.getCharHeight();
1649 if (adjustPanelHeight)
1651 int alignmentHeight = rowHeight * av.getAlignment().getHeight();
1654 * If not enough vertical space, maximize annotation height while keeping
1655 * at least two rows of alignment visible
1657 if (annotationHeight + alignmentHeight > availableHeight)
1659 annotationHeight = Math.min(annotationHeight,
1660 availableHeight - 2 * rowHeight);
1665 // maintain same window layout whilst updating sliders
1666 annotationHeight = Math.min(ap.annotationScroller.getSize().height,
1667 availableHeight - 2 * rowHeight);
1669 return annotationHeight;