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.
21 package jalview.appletgui;
23 import jalview.datamodel.AlignmentAnnotation;
24 import jalview.datamodel.Annotation;
25 import jalview.datamodel.SequenceI;
26 import jalview.renderer.AnnotationRenderer;
27 import jalview.renderer.AwtRenderPanelI;
28 import jalview.schemes.ResidueProperties;
29 import jalview.util.Comparison;
30 import jalview.util.MessageManager;
31 import jalview.util.Platform;
32 import jalview.viewmodel.ViewportListenerI;
33 import jalview.viewmodel.ViewportRanges;
35 import java.awt.Color;
36 import java.awt.Dimension;
38 import java.awt.FontMetrics;
39 import java.awt.Graphics;
40 import java.awt.Image;
41 import java.awt.MenuItem;
42 import java.awt.Panel;
43 import java.awt.PopupMenu;
44 import java.awt.event.ActionEvent;
45 import java.awt.event.ActionListener;
46 import java.awt.event.AdjustmentEvent;
47 import java.awt.event.AdjustmentListener;
48 import java.awt.event.InputEvent;
49 import java.awt.event.MouseEvent;
50 import java.awt.event.MouseListener;
51 import java.awt.event.MouseMotionListener;
52 import java.beans.PropertyChangeEvent;
54 import jalview.datamodel.AlignmentAnnotation;
55 import jalview.datamodel.Annotation;
56 import jalview.datamodel.SequenceI;
57 import jalview.renderer.AnnotationRenderer;
58 import jalview.renderer.AwtRenderPanelI;
59 import jalview.schemes.ResidueProperties;
60 import jalview.util.Comparison;
61 import jalview.util.MessageManager;
62 import jalview.viewmodel.ViewportListenerI;
63 import jalview.viewmodel.ViewportRanges;
65 public class AnnotationPanel extends Panel
66 implements AwtRenderPanelI, AdjustmentListener, ActionListener,
67 MouseListener, MouseMotionListener, ViewportListenerI
75 final String HELIX = "Helix";
77 final String SHEET = "Sheet";
80 * For RNA secondary structure "stems" aka helices
82 final String STEM = "RNA Helix";
84 final String LABEL = "Label";
86 final String REMOVE = "Remove Annotation";
88 final String COLOUR = "Colour";
90 final Color HELIX_COLOUR = Color.red.darker();
92 final Color SHEET_COLOUR = Color.green.darker().darker();
102 boolean fastPaint = false;
104 // Used For mouse Dragging and resizing graphs
105 int graphStretch = -1;
107 int graphStretchY = -1;
109 boolean mouseDragging = false;
111 public static int GRAPH_HEIGHT = 40;
113 // boolean MAC = false;
115 public final AnnotationRenderer renderer;
117 public AnnotationPanel(AlignmentPanel ap)
119 new jalview.util.Platform();
120 // MAC = Platform.isAMac();
124 int height = adjustPanelHeight();
125 ap.apvscroll.setValues(0, getSize().height, 0, height);
127 addMouseMotionListener(this);
129 addMouseListener(this);
131 // ap.annotationScroller.getVAdjustable().addAdjustmentListener( this );
132 renderer = new AnnotationRenderer();
134 av.getRanges().addPropertyChangeListener(this);
137 public AnnotationPanel(AlignViewport av)
140 renderer = new AnnotationRenderer();
145 public void adjustmentValueChanged(AdjustmentEvent evt)
156 public void actionPerformed(ActionEvent evt)
158 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
163 Annotation[] anot = aa[activeRow].annotations;
165 if (anot.length < av.getColumnSelection().getMax())
167 Annotation[] temp = new Annotation[av.getColumnSelection().getMax()
169 System.arraycopy(anot, 0, temp, 0, anot.length);
171 aa[activeRow].annotations = anot;
175 if (av.getColumnSelection() != null
176 && !av.getColumnSelection().isEmpty()
177 && anot[av.getColumnSelection().getMin()] != null)
179 label = anot[av.getColumnSelection().getMin()].displayCharacter;
182 if (evt.getActionCommand().equals(REMOVE))
184 for (int index : av.getColumnSelection().getSelected())
186 if (av.getAlignment().getHiddenColumns().isVisible(index))
192 else if (evt.getActionCommand().equals(LABEL))
194 label = enterLabel(label, "Enter Label");
201 if ((label.length() > 0) && !aa[activeRow].hasText)
203 aa[activeRow].hasText = true;
206 for (int index : av.getColumnSelection().getSelected())
208 // TODO: JAL-2001 - provide a fast method to list visible selected
210 if (!av.getAlignment().getHiddenColumns().isVisible(index))
215 if (anot[index] == null)
217 anot[index] = new Annotation(label, "", ' ', 0);
220 anot[index].displayCharacter = label;
223 else if (evt.getActionCommand().equals(COLOUR))
225 UserDefinedColours udc = new UserDefinedColours(this, Color.black,
228 Color col = udc.getColor();
230 for (int index : av.getColumnSelection().getSelected())
232 if (!av.getAlignment().getHiddenColumns().isVisible(index))
237 if (anot[index] == null)
239 anot[index] = new Annotation("", "", ' ', 0);
242 anot[index].colour = col;
249 String symbol = "\u03B1";
251 if (evt.getActionCommand().equals(HELIX))
255 else if (evt.getActionCommand().equals(SHEET))
261 // Added by LML to color stems
262 else if (evt.getActionCommand().equals(STEM))
265 int column = av.getColumnSelection().getSelectedRanges().get(0)[0];
266 symbol = aa[activeRow].getDefaultRnaHelixSymbol(column);
269 if (!aa[activeRow].hasIcons)
271 aa[activeRow].hasIcons = true;
274 label = enterLabel(symbol, "Enter Label");
281 if ((label.length() > 0) && !aa[activeRow].hasText)
283 aa[activeRow].hasText = true;
284 if (evt.getActionCommand().equals(STEM))
286 aa[activeRow].showAllColLabels = true;
290 for (int index : av.getColumnSelection().getSelected())
292 if (!av.getAlignment().getHiddenColumns().isVisible(index))
297 if (anot[index] == null)
299 anot[index] = new Annotation(label, "", type, 0);
302 anot[index].secondaryStructure = type != 'S' ? type
303 : label.length() == 0 ? ' ' : label.charAt(0);
304 anot[index].displayCharacter = label;
308 av.getAlignment().validateAnnotation(aa[activeRow]);
310 ap.alignmentChanged();
317 String enterLabel(String text, String label)
319 EditNameDialog dialog = new EditNameDialog(text, null, label, null,
320 ap.alignFrame, "Enter Label", 400, 200, true);
324 return dialog.getName();
333 public void mousePressed(MouseEvent evt)
335 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
341 int height = -scrollOffset;
344 for (int i = 0; i < aa.length; i++)
348 height += aa[i].height;
351 if (evt.getY() < height)
357 else if (aa[i].graph > 0)
361 graphStretchY = evt.getY();
368 if ((evt.getModifiersEx()
369 & InputEvent.BUTTON3_DOWN_MASK) == InputEvent.BUTTON3_DOWN_MASK
372 if (av.getColumnSelection() == null
373 || av.getColumnSelection().isEmpty())
378 PopupMenu pop = new PopupMenu(
379 MessageManager.getString("label.structure_type"));
382 if (av.getAlignment().isNucleotide())
384 item = new MenuItem(STEM);
385 item.addActionListener(this);
390 item = new MenuItem(HELIX);
391 item.addActionListener(this);
393 item = new MenuItem(SHEET);
394 item.addActionListener(this);
397 item = new MenuItem(LABEL);
398 item.addActionListener(this);
400 item = new MenuItem(COLOUR);
401 item.addActionListener(this);
403 item = new MenuItem(REMOVE);
404 item.addActionListener(this);
406 ap.alignFrame.add(pop);
407 pop.show(this, evt.getX(), evt.getY());
412 ap.scalePanel.mousePressed(evt);
416 public void mouseReleased(MouseEvent evt)
420 mouseDragging = false;
424 needValidating = false;
426 ap.scalePanel.mouseReleased(evt);
430 public void mouseClicked(MouseEvent evt)
434 boolean needValidating = false;
437 public void mouseDragged(MouseEvent evt)
439 if (graphStretch > -1)
442 .getAlignmentAnnotation()[graphStretch].graphHeight += graphStretchY
444 if (av.getAlignment()
445 .getAlignmentAnnotation()[graphStretch].graphHeight < 0)
448 .getAlignmentAnnotation()[graphStretch].graphHeight = 0;
450 graphStretchY = evt.getY();
451 av.calcPanelHeight();
452 needValidating = true;
453 // TODO: only update overview visible geometry
454 ap.paintAlignment(true, false);
458 ap.scalePanel.mouseDragged(evt);
463 public void mouseMoved(MouseEvent evt)
465 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
472 int height = -scrollOffset;
473 for (int i = 0; i < aa.length; i++)
478 height += aa[i].height;
481 if (evt.getY() < height)
488 int column = evt.getX() / av.getCharWidth()
489 + av.getRanges().getStartRes();
491 if (av.hasHiddenColumns())
493 column = av.getAlignment().getHiddenColumns()
494 .visibleToAbsoluteColumn(column);
497 if (row > -1 && column < aa[row].annotations.length
498 && aa[row].annotations[column] != null)
500 StringBuilder text = new StringBuilder();
501 text.append(MessageManager.getString("label.column")).append(" ")
503 String description = aa[row].annotations[column].description;
504 if (description != null && description.length() > 0)
506 text.append(" ").append(description);
510 * if the annotation is sequence-specific, show the sequence number
511 * in the alignment, and (if not a gap) the residue and position
513 SequenceI seqref = aa[row].sequenceRef;
516 int seqIndex = av.getAlignment().findIndex(seqref);
520 .append(MessageManager.getString("label.sequence"))
521 .append(" ").append(seqIndex + 1);
522 char residue = seqref.getCharAt(column);
523 if (!Comparison.isGap(residue))
527 if (av.getAlignment().isNucleotide())
529 name = ResidueProperties.nucleotideName
530 .get(String.valueOf(residue));
531 text.append(" Nucleotide: ")
532 .append(name != null ? name : residue);
536 name = 'X' == residue ? "X"
537 : ('*' == residue ? "STOP"
538 : ResidueProperties.aa2Triplet
539 .get(String.valueOf(residue)));
540 text.append(" Residue: ")
541 .append(name != null ? name : residue);
543 int residuePos = seqref.findPosition(column);
544 text.append(" (").append(residuePos).append(")");
545 // int residuePos = seqref.findPosition(column);
546 // text.append(residue).append(" (")
547 // .append(residuePos).append(")");
552 ap.alignFrame.statusBar.setText(text.toString());
557 public void mouseEntered(MouseEvent evt)
559 ap.scalePanel.mouseEntered(evt);
563 public void mouseExited(MouseEvent evt)
565 ap.scalePanel.mouseExited(evt);
568 public int adjustPanelHeight()
570 return adjustPanelHeight(true);
573 public int adjustPanelHeight(boolean repaint)
575 int height = av.calcPanelHeight();
576 this.setSize(new Dimension(getSize().width, height));
585 * calculate the height for visible annotation, revalidating bounds where
586 * necessary ABSTRACT GUI METHOD
588 * @return total height of annotation
591 public void addEditableColumn(int i)
595 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
601 for (int j = 0; j < aa.length; j++)
613 public void update(Graphics g)
619 public void paint(Graphics g)
621 Dimension d = getSize();
623 // (av.endRes - av.startRes + 1) * av.charWidth;
624 if (imgWidth < 1 || d.height < 1)
628 if (image == null || imgWidth != image.getWidth(this)
629 || d.height != image.getHeight(this))
631 image = createImage(imgWidth, d.height);
632 gg = image.getGraphics();
633 gg.setFont(av.getFont());
634 fm = gg.getFontMetrics();
640 g.drawImage(image, 0, 0, this);
645 gg.setColor(Color.white);
646 gg.fillRect(0, 0, getSize().width, getSize().height);
647 drawComponent(gg, av.getRanges().getStartRes(),
648 av.getRanges().getEndRes() + 1);
650 g.drawImage(image, 0, 0, this);
653 public void fastPaint(int horizontal)
655 if (horizontal == 0 || gg == null
656 || av.getAlignment().getAlignmentAnnotation() == null
657 || av.getAlignment().getAlignmentAnnotation().length < 1)
663 gg.copyArea(0, 0, imgWidth, getSize().height,
664 -horizontal * av.getCharWidth(), 0);
665 int sr = av.getRanges().getStartRes(),
666 er = av.getRanges().getEndRes() + 1, transX = 0;
668 if (horizontal > 0) // scrollbar pulled right, image to the left
670 transX = (er - sr - horizontal) * av.getCharWidth();
671 sr = er - horizontal;
673 else if (horizontal < 0)
675 er = sr - horizontal;
678 gg.translate(transX, 0);
680 drawComponent(gg, sr, er);
682 gg.translate(-transX, 0);
698 public void drawComponent(Graphics g, int startRes, int endRes)
700 Font ofont = av.getFont();
703 g.setColor(Color.white);
704 g.fillRect(0, 0, (endRes - startRes) * av.getCharWidth(),
709 fm = g.getFontMetrics();
712 if ((av.getAlignment().getAlignmentAnnotation() == null)
713 || (av.getAlignment().getAlignmentAnnotation().length < 1))
715 g.setColor(Color.white);
716 g.fillRect(0, 0, getSize().width, getSize().height);
717 g.setColor(Color.black);
718 if (av.validCharWidth)
720 g.drawString(MessageManager
721 .getString("label.alignment_has_no_annotations"), 20, 15);
726 g.translate(0, -scrollOffset);
727 renderer.drawComponent(this, av, g, activeRow, startRes, endRes);
728 g.translate(0, +scrollOffset);
731 int scrollOffset = 0;
733 public void setScrollOffset(int value, boolean repaint)
735 scrollOffset = value;
743 public FontMetrics getFontMetrics()
749 public Image getFadedImage()
755 public int getFadedImageWidth()
760 private int[] bounds = new int[2];
763 public int[] getVisibleVRange()
765 if (ap != null && ap.alabels != null)
767 int sOffset = -ap.alabels.scrollOffset;
768 int visHeight = sOffset + ap.annotationPanelHolder.getHeight();
770 bounds[1] = visHeight;
780 public void propertyChange(PropertyChangeEvent evt)
782 // Respond to viewport range changes (e.g. alignment panel was scrolled)
783 // Both scrolling and resizing change viewport ranges: scrolling changes
784 // both start and end points, but resize only changes end values.
785 // Here we only want to fastpaint on a scroll, with resize using a normal
786 // paint, so scroll events are identified as changes to the horizontal or
787 // vertical start value.
788 if (evt.getPropertyName().equals(ViewportRanges.STARTRES))
790 fastPaint((int) evt.getNewValue() - (int) evt.getOldValue());
792 else if (evt.getPropertyName().equals(ViewportRanges.STARTRESANDSEQ))
794 fastPaint(((int[]) evt.getNewValue())[0]
795 - ((int[]) evt.getOldValue())[0]);
797 else if (evt.getPropertyName().equals(ViewportRanges.MOVE_VIEWPORT))