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 java.awt.Color;
24 import java.awt.Dimension;
26 import java.awt.FontMetrics;
27 import java.awt.Graphics;
28 import java.awt.Image;
29 import java.awt.MenuItem;
30 import java.awt.Panel;
31 import java.awt.PopupMenu;
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.InputEvent;
37 import java.awt.event.MouseEvent;
38 import java.awt.event.MouseListener;
39 import java.awt.event.MouseMotionListener;
40 import java.beans.PropertyChangeEvent;
42 import jalview.datamodel.AlignmentAnnotation;
43 import jalview.datamodel.Annotation;
44 import jalview.datamodel.SequenceI;
45 import jalview.renderer.AnnotationRenderer;
46 import jalview.renderer.AwtRenderPanelI;
47 import jalview.schemes.ResidueProperties;
48 import jalview.util.Comparison;
49 import jalview.util.MessageManager;
50 import jalview.viewmodel.ViewportListenerI;
51 import jalview.viewmodel.ViewportRanges;
53 public class AnnotationPanel extends Panel
54 implements AwtRenderPanelI, AdjustmentListener, ActionListener,
55 MouseListener, MouseMotionListener, ViewportListenerI
63 final String HELIX = "Helix";
65 final String SHEET = "Sheet";
68 * For RNA secondary structure "stems" aka helices
70 final String STEM = "RNA Helix";
72 final String LABEL = "Label";
74 final String REMOVE = "Remove Annotation";
76 final String COLOUR = "Colour";
78 final Color HELIX_COLOUR = Color.red.darker();
80 final Color SHEET_COLOUR = Color.green.darker().darker();
90 boolean fastPaint = false;
92 // Used For mouse Dragging and resizing graphs
93 int graphStretch = -1;
95 int graphStretchY = -1;
97 boolean mouseDragging = false;
99 public static int GRAPH_HEIGHT = 40;
101 // boolean MAC = false;
103 public final AnnotationRenderer renderer;
105 public AnnotationPanel(AlignmentPanel ap)
107 new jalview.util.Platform();
108 // MAC = Platform.isAMac();
112 int height = adjustPanelHeight();
113 ap.apvscroll.setValues(0, getSize().height, 0, height);
115 addMouseMotionListener(this);
117 addMouseListener(this);
119 // ap.annotationScroller.getVAdjustable().addAdjustmentListener( this );
120 renderer = new AnnotationRenderer();
122 av.getRanges().addPropertyChangeListener(this);
125 public AnnotationPanel(AlignViewport av)
128 renderer = new AnnotationRenderer();
133 public void adjustmentValueChanged(AdjustmentEvent evt)
144 public void actionPerformed(ActionEvent evt)
146 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
151 Annotation[] anot = aa[activeRow].annotations;
153 if (anot.length < av.getColumnSelection().getMax())
155 Annotation[] temp = new Annotation[av.getColumnSelection().getMax()
157 System.arraycopy(anot, 0, temp, 0, anot.length);
159 aa[activeRow].annotations = anot;
163 if (av.getColumnSelection() != null
164 && !av.getColumnSelection().isEmpty()
165 && anot[av.getColumnSelection().getMin()] != null)
167 label = anot[av.getColumnSelection().getMin()].displayCharacter;
170 if (evt.getActionCommand().equals(REMOVE))
172 for (int index : av.getColumnSelection().getSelected())
174 if (av.getAlignment().getHiddenColumns().isVisible(index))
180 else if (evt.getActionCommand().equals(LABEL))
182 label = enterLabel(label, "Enter Label");
189 if ((label.length() > 0) && !aa[activeRow].hasText)
191 aa[activeRow].hasText = true;
194 for (int index : av.getColumnSelection().getSelected())
196 // TODO: JAL-2001 - provide a fast method to list visible selected
198 if (!av.getAlignment().getHiddenColumns().isVisible(index))
203 if (anot[index] == null)
205 anot[index] = new Annotation(label, "", ' ', 0);
208 anot[index].displayCharacter = label;
211 else if (evt.getActionCommand().equals(COLOUR))
213 UserDefinedColours udc = new UserDefinedColours(this, Color.black,
216 Color col = udc.getColor();
218 for (int index : av.getColumnSelection().getSelected())
220 if (!av.getAlignment().getHiddenColumns().isVisible(index))
225 if (anot[index] == null)
227 anot[index] = new Annotation("", "", ' ', 0);
230 anot[index].colour = col;
237 String symbol = "\u03B1";
239 if (evt.getActionCommand().equals(HELIX))
243 else if (evt.getActionCommand().equals(SHEET))
249 // Added by LML to color stems
250 else if (evt.getActionCommand().equals(STEM))
253 int column = av.getColumnSelection().getSelectedRanges().get(0)[0];
254 symbol = aa[activeRow].getDefaultRnaHelixSymbol(column);
257 if (!aa[activeRow].hasIcons)
259 aa[activeRow].hasIcons = true;
262 label = enterLabel(symbol, "Enter Label");
269 if ((label.length() > 0) && !aa[activeRow].hasText)
271 aa[activeRow].hasText = true;
272 if (evt.getActionCommand().equals(STEM))
274 aa[activeRow].showAllColLabels = true;
278 for (int index : av.getColumnSelection().getSelected())
280 if (!av.getAlignment().getHiddenColumns().isVisible(index))
285 if (anot[index] == null)
287 anot[index] = new Annotation(label, "", type, 0);
290 anot[index].secondaryStructure = type != 'S' ? type
291 : label.length() == 0 ? ' ' : label.charAt(0);
292 anot[index].displayCharacter = label;
296 av.getAlignment().validateAnnotation(aa[activeRow]);
298 ap.alignmentChanged();
305 String enterLabel(String text, String label)
307 EditNameDialog dialog = new EditNameDialog(text, null, label, null,
308 ap.alignFrame, "Enter Label", 400, 200, true);
312 return dialog.getName();
321 public void mousePressed(MouseEvent evt)
323 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
329 int height = -scrollOffset;
332 for (int i = 0; i < aa.length; i++)
336 height += aa[i].height;
339 if (evt.getY() < height)
345 else if (aa[i].graph > 0)
349 graphStretchY = evt.getY();
356 if ((evt.getModifiersEx()
357 & InputEvent.BUTTON3_DOWN_MASK) == InputEvent.BUTTON3_DOWN_MASK
360 if (av.getColumnSelection() == null
361 || av.getColumnSelection().isEmpty())
366 PopupMenu pop = new PopupMenu(
367 MessageManager.getString("label.structure_type"));
370 if (av.getAlignment().isNucleotide())
372 item = new MenuItem(STEM);
373 item.addActionListener(this);
378 item = new MenuItem(HELIX);
379 item.addActionListener(this);
381 item = new MenuItem(SHEET);
382 item.addActionListener(this);
385 item = new MenuItem(LABEL);
386 item.addActionListener(this);
388 item = new MenuItem(COLOUR);
389 item.addActionListener(this);
391 item = new MenuItem(REMOVE);
392 item.addActionListener(this);
394 ap.alignFrame.add(pop);
395 pop.show(this, evt.getX(), evt.getY());
400 ap.scalePanel.mousePressed(evt);
404 public void mouseReleased(MouseEvent evt)
408 mouseDragging = false;
412 needValidating = false;
414 ap.scalePanel.mouseReleased(evt);
418 public void mouseClicked(MouseEvent evt)
422 boolean needValidating = false;
425 public void mouseDragged(MouseEvent evt)
427 if (graphStretch > -1)
430 .getAlignmentAnnotation()[graphStretch].graphHeight += graphStretchY
432 if (av.getAlignment()
433 .getAlignmentAnnotation()[graphStretch].graphHeight < 0)
436 .getAlignmentAnnotation()[graphStretch].graphHeight = 0;
438 graphStretchY = evt.getY();
439 av.calcPanelHeight();
440 needValidating = true;
441 // TODO: only update overview visible geometry
442 ap.paintAlignment(true, false);
446 ap.scalePanel.mouseDragged(evt);
451 public void mouseMoved(MouseEvent evt)
453 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
460 int height = -scrollOffset;
461 for (int i = 0; i < aa.length; i++)
466 height += aa[i].height;
469 if (evt.getY() < height)
476 int column = evt.getX() / av.getCharWidth()
477 + av.getRanges().getStartRes();
479 if (av.hasHiddenColumns())
481 column = av.getAlignment().getHiddenColumns()
482 .visibleToAbsoluteColumn(column);
485 if (row > -1 && column < aa[row].annotations.length
486 && aa[row].annotations[column] != null)
488 StringBuilder text = new StringBuilder();
489 text.append(MessageManager.getString("label.column")).append(" ")
491 String description = aa[row].annotations[column].description;
492 if (description != null && description.length() > 0)
494 text.append(" ").append(description);
498 * if the annotation is sequence-specific, show the sequence number
499 * in the alignment, and (if not a gap) the residue and position
501 SequenceI seqref = aa[row].sequenceRef;
504 int seqIndex = av.getAlignment().findIndex(seqref);
508 .append(MessageManager.getString("label.sequence"))
509 .append(" ").append(seqIndex + 1);
510 char residue = seqref.getCharAt(column);
511 if (!Comparison.isGap(residue))
515 if (av.getAlignment().isNucleotide())
517 name = ResidueProperties.nucleotideName
518 .get(String.valueOf(residue));
519 text.append(" Nucleotide: ")
520 .append(name != null ? name : residue);
524 name = 'X' == residue ? "X"
525 : ('*' == residue ? "STOP"
526 : ResidueProperties.aa2Triplet
527 .get(String.valueOf(residue)));
528 text.append(" Residue: ")
529 .append(name != null ? name : residue);
531 int residuePos = seqref.findPosition(column);
532 text.append(" (").append(residuePos).append(")");
533 // int residuePos = seqref.findPosition(column);
534 // text.append(residue).append(" (")
535 // .append(residuePos).append(")");
540 ap.alignFrame.statusBar.setText(text.toString());
545 public void mouseEntered(MouseEvent evt)
547 ap.scalePanel.mouseEntered(evt);
551 public void mouseExited(MouseEvent evt)
553 ap.scalePanel.mouseExited(evt);
556 public int adjustPanelHeight()
558 return adjustPanelHeight(true);
561 public int adjustPanelHeight(boolean repaint)
563 int height = av.calcPanelHeight();
564 this.setSize(new Dimension(getSize().width, height));
573 * calculate the height for visible annotation, revalidating bounds where
574 * necessary ABSTRACT GUI METHOD
576 * @return total height of annotation
579 public void addEditableColumn(int i)
583 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
589 for (int j = 0; j < aa.length; j++)
601 public void update(Graphics g)
607 public void paint(Graphics g)
609 Dimension d = getSize();
611 // (av.endRes - av.startRes + 1) * av.charWidth;
612 if (imgWidth < 1 || d.height < 1)
616 if (image == null || imgWidth != image.getWidth(this)
617 || d.height != image.getHeight(this))
619 image = createImage(imgWidth, d.height);
620 gg = image.getGraphics();
621 gg.setFont(av.getFont());
622 fm = gg.getFontMetrics();
628 g.drawImage(image, 0, 0, this);
633 gg.setColor(Color.white);
634 gg.fillRect(0, 0, getSize().width, getSize().height);
635 drawComponent(gg, av.getRanges().getStartRes(),
636 av.getRanges().getEndRes() + 1);
638 g.drawImage(image, 0, 0, this);
641 public void fastPaint(int horizontal)
643 if (horizontal == 0 || gg == null
644 || av.getAlignment().getAlignmentAnnotation() == null
645 || av.getAlignment().getAlignmentAnnotation().length < 1)
651 gg.copyArea(0, 0, imgWidth, getSize().height,
652 -horizontal * av.getCharWidth(), 0);
653 int sr = av.getRanges().getStartRes(),
654 er = av.getRanges().getEndRes() + 1, transX = 0;
656 if (horizontal > 0) // scrollbar pulled right, image to the left
658 transX = (er - sr - horizontal) * av.getCharWidth();
659 sr = er - horizontal;
661 else if (horizontal < 0)
663 er = sr - horizontal;
666 gg.translate(transX, 0);
668 drawComponent(gg, sr, er);
670 gg.translate(-transX, 0);
686 public void drawComponent(Graphics g, int startRes, int endRes)
688 Font ofont = av.getFont();
691 g.setColor(Color.white);
692 g.fillRect(0, 0, (endRes - startRes) * av.getCharWidth(),
697 fm = g.getFontMetrics();
700 if ((av.getAlignment().getAlignmentAnnotation() == null)
701 || (av.getAlignment().getAlignmentAnnotation().length < 1))
703 g.setColor(Color.white);
704 g.fillRect(0, 0, getSize().width, getSize().height);
705 g.setColor(Color.black);
706 if (av.validCharWidth)
708 g.drawString(MessageManager
709 .getString("label.alignment_has_no_annotations"), 20, 15);
714 g.translate(0, -scrollOffset);
715 renderer.drawComponent(this, av, g, activeRow, startRes, endRes);
716 g.translate(0, +scrollOffset);
719 int scrollOffset = 0;
721 public void setScrollOffset(int value, boolean repaint)
723 scrollOffset = value;
731 public FontMetrics getFontMetrics()
737 public Image getFadedImage()
743 public int getFadedImageWidth()
748 private int[] bounds = new int[2];
751 public int[] getVisibleVRange()
753 if (ap != null && ap.alabels != null)
755 int sOffset = -ap.alabels.scrollOffset;
756 int visHeight = sOffset + ap.annotationPanelHolder.getHeight();
758 bounds[1] = visHeight;
768 public void propertyChange(PropertyChangeEvent evt)
770 // Respond to viewport range changes (e.g. alignment panel was scrolled)
771 // Both scrolling and resizing change viewport ranges: scrolling changes
772 // both start and end points, but resize only changes end values.
773 // Here we only want to fastpaint on a scroll, with resize using a normal
774 // paint, so scroll events are identified as changes to the horizontal or
775 // vertical start value.
776 if (evt.getPropertyName().equals(ViewportRanges.STARTRES))
778 fastPaint((int) evt.getNewValue() - (int) evt.getOldValue());
780 else if (evt.getPropertyName().equals(ViewportRanges.STARTRESANDSEQ))
782 fastPaint(((int[]) evt.getNewValue())[0]
783 - ((int[]) evt.getOldValue())[0]);
785 else if (evt.getPropertyName().equals(ViewportRanges.MOVE_VIEWPORT))