2 * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8)
3 * Copyright (C) 2012 J Procter, AM Waterhouse, LM Lui, J Engelhardt, G Barton, M Clamp, S Searle
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 of the License, or (at your option) any later version.
11 * Jalview is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty
13 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
14 * PURPOSE. See the GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along with Jalview. If not, see <http://www.gnu.org/licenses/>.
21 import java.util.regex.Pattern;
24 import java.awt.datatransfer.*;
25 import java.awt.event.*;
26 import java.awt.geom.AffineTransform;
27 import java.awt.image.*;
30 import jalview.datamodel.*;
32 import jalview.util.MessageManager;
40 public class AnnotationLabels extends JPanel implements MouseListener,
41 MouseMotionListener, ActionListener
43 static String TOGGLE_LABELSCALE = "Scale Label to Column";
45 static String ADDNEW = "Add New Row";
47 static String EDITNAME = "Edit Label/Description";
49 static String HIDE = "Hide This Row";
51 static String DELETE = "Delete This Row";
53 static String SHOWALL = "Show All Hidden Rows";
55 static String OUTPUT_TEXT = "Export Annotation";
57 static String COPYCONS_SEQ = "Copy Consensus Sequence";
59 boolean resizePanel = false;
67 boolean resizing = false;
77 Font font = new Font("Arial", Font.PLAIN, 11);
79 private boolean hasHiddenRows;
82 * Creates a new AnnotationLabels object.
87 public AnnotationLabels(AlignmentPanel ap)
91 ToolTipManager.sharedInstance().registerComponent(this);
93 java.net.URL url = getClass().getResource("/images/idwidth.gif");
98 temp = java.awt.Toolkit.getDefaultToolkit().createImage(url);
103 MediaTracker mt = new MediaTracker(this);
104 mt.addImage(temp, 0);
106 } catch (Exception ex)
110 BufferedImage bi = new BufferedImage(temp.getHeight(this),
111 temp.getWidth(this), BufferedImage.TYPE_INT_RGB);
112 Graphics2D g = (Graphics2D) bi.getGraphics();
113 g.rotate(Math.toRadians(90));
114 g.drawImage(temp, 0, -bi.getWidth(this), this);
117 addMouseListener(this);
118 addMouseMotionListener(this);
119 addMouseWheelListener(ap.annotationPanel);
122 public AnnotationLabels(AlignViewport av)
133 public void setScrollOffset(int y)
140 * sets selectedRow to -2 if no annotation preset, -1 if no visible row is at
144 * coordinate position to search for a row
146 void getSelectedRow(int y)
149 AlignmentAnnotation[] aa = ap.av.getAlignment()
150 .getAlignmentAnnotation();
154 for (int i = 0; i < aa.length; i++)
162 height += aa[i].height;
180 public void actionPerformed(ActionEvent evt)
182 AlignmentAnnotation[] aa = ap.av.getAlignment()
183 .getAlignmentAnnotation();
185 if (evt.getActionCommand().equals(ADDNEW))
187 AlignmentAnnotation newAnnotation = new AlignmentAnnotation(null,
188 null, new Annotation[ap.av.getAlignment().getWidth()]);
190 if (!editLabelDescription(newAnnotation))
195 ap.av.getAlignment().addAnnotation(newAnnotation);
196 ap.av.getAlignment().setAnnotationIndex(newAnnotation, 0);
198 else if (evt.getActionCommand().equals(EDITNAME))
200 editLabelDescription(aa[selectedRow]);
203 else if (evt.getActionCommand().equals(HIDE))
205 aa[selectedRow].visible = false;
207 else if (evt.getActionCommand().equals(DELETE))
209 ap.av.getAlignment().deleteAnnotation(aa[selectedRow]);
211 else if (evt.getActionCommand().equals(SHOWALL))
213 for (int i = 0; i < aa.length; i++)
215 if (!aa[i].visible && aa[i].annotations != null)
217 aa[i].visible = true;
221 else if (evt.getActionCommand().equals(OUTPUT_TEXT))
223 new AnnotationExporter().exportAnnotations(ap,
224 new AlignmentAnnotation[]
225 { aa[selectedRow] }, null, null);
227 else if (evt.getActionCommand().equals(COPYCONS_SEQ))
229 SequenceI cons = null;
230 if (aa[selectedRow].groupRef != null)
232 cons = aa[selectedRow].groupRef.getConsensusSeq();
236 cons = av.getConsensusSeq();
240 copy_annotseqtoclipboard(cons);
244 else if (evt.getActionCommand().equals(TOGGLE_LABELSCALE))
246 aa[selectedRow].scaleColLabel = !aa[selectedRow].scaleColLabel;
249 ap.validateAnnotationDimensions(false);
253 // ap.paintAlignment(true);
262 boolean editLabelDescription(AlignmentAnnotation annotation)
264 EditNameDialog dialog = new EditNameDialog(annotation.label,
265 annotation.description, " Annotation Name ",
266 "Annotation Description ", "Edit Annotation Name/Description",
274 annotation.label = dialog.getName();
276 String text = dialog.getDescription();
277 if (text != null && text.length() == 0)
281 annotation.description = text;
292 public void mousePressed(MouseEvent evt)
294 getSelectedRow(evt.getY() - scrollOffset);
304 public void mouseReleased(MouseEvent evt)
306 int start = selectedRow;
307 getSelectedRow(evt.getY() - scrollOffset);
308 int end = selectedRow;
312 // Swap these annotations
313 AlignmentAnnotation startAA = ap.av.getAlignment()
314 .getAlignmentAnnotation()[start];
317 end = ap.av.getAlignment().getAlignmentAnnotation().length - 1;
319 AlignmentAnnotation endAA = ap.av.getAlignment()
320 .getAlignmentAnnotation()[end];
322 ap.av.getAlignment().getAlignmentAnnotation()[end] = startAA;
323 ap.av.getAlignment().getAlignmentAnnotation()[start] = endAA;
329 ap.annotationPanel.repaint();
338 public void mouseEntered(MouseEvent evt)
353 public void mouseExited(MouseEvent evt)
355 if (dragEvent == null)
368 public void mouseDragged(MouseEvent evt)
374 Dimension d = ap.annotationScroller.getPreferredSize();
375 int dif = evt.getY() - oldY;
377 dif /= ap.av.charHeight;
378 dif *= ap.av.charHeight;
380 if ((d.height - dif) > 20)
382 ap.annotationScroller.setPreferredSize(new Dimension(d.width,
384 d = ap.annotationSpaceFillerHolder.getPreferredSize();
385 ap.annotationSpaceFillerHolder.setPreferredSize(new Dimension(
386 d.width, d.height - dif));
387 ap.paintAlignment(true);
404 public void mouseMoved(MouseEvent evt)
406 resizePanel = evt.getY() < 10;
408 getSelectedRow(evt.getY() - scrollOffset);
411 && ap.av.getAlignment().getAlignmentAnnotation().length > selectedRow)
413 AlignmentAnnotation aa = ap.av.getAlignment()
414 .getAlignmentAnnotation()[selectedRow];
416 StringBuffer desc = new StringBuffer();
417 if (aa.description != null
418 && !aa.description.equals("New description"))
420 // TODO: we could refactor and merge this code with the code in
421 // jalview.gui.SeqPanel.mouseMoved(..) that formats sequence feature
423 desc.append(aa.getDescription(true).trim());
424 // check to see if the description is an html fragment.
425 if (desc.length() < 6
426 || (desc.substring(0, 6).toLowerCase().indexOf("<html>") < 0))
428 // clean the description ready for embedding in html
429 desc = new StringBuffer(Pattern.compile("<").matcher(desc)
430 .replaceAll("<"));
431 desc.insert(0, "<html>");
435 // remove terminating html if any
436 int i = desc.substring(desc.length() - 7).toLowerCase()
437 .lastIndexOf("</html>");
440 desc.setLength(desc.length() - 7 + i);
445 desc.append("<br/>");
451 // begin the tooltip's html fragment
452 desc.append("<html>");
456 // TODO: limit precision of score to avoid noise from imprecise doubles
457 // (64.7 becomes 64.7+/some tiny value).
458 desc.append(" Score: " + aa.score);
461 if (desc.length() > 6)
463 desc.append("</html>");
464 this.setToolTipText(desc.toString());
467 this.setToolTipText(null);
478 public void mouseClicked(MouseEvent evt)
480 AlignmentAnnotation[] aa = ap.av.getAlignment()
481 .getAlignmentAnnotation();
482 if (SwingUtilities.isLeftMouseButton(evt))
484 if (selectedRow > -1 && selectedRow < aa.length)
486 if (aa[selectedRow].groupRef != null)
488 if (evt.getClickCount() >= 2)
490 // todo: make the ap scroll to the selection - not necessary, first
491 // click highlights/scrolls, second selects
492 ap.seqPanel.ap.idPanel.highlightSearchResults(null);
493 ap.av.setSelectionGroup(// new SequenceGroup(
494 aa[selectedRow].groupRef); // );
495 ap.paintAlignment(false);
496 PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
497 ap.av.sendSelection();
501 ap.seqPanel.ap.idPanel
502 .highlightSearchResults(aa[selectedRow].groupRef
503 .getSequences(null));
507 else if (aa[selectedRow].sequenceRef != null)
509 Vector sr = new Vector();
510 sr.addElement(aa[selectedRow].sequenceRef);
511 if (evt.getClickCount() == 1)
513 ap.seqPanel.ap.idPanel.highlightSearchResults(sr);
515 else if (evt.getClickCount() >= 2)
517 ap.seqPanel.ap.idPanel.highlightSearchResults(null);
518 SequenceGroup sg = new SequenceGroup();
519 sg.addSequence(aa[selectedRow].sequenceRef, false);
520 ap.av.setSelectionGroup(sg);
521 ap.av.sendSelection();
522 ap.paintAlignment(false);
523 PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
529 if (!SwingUtilities.isRightMouseButton(evt))
534 JPopupMenu pop = new JPopupMenu("Annotations");
535 JMenuItem item = new JMenuItem(ADDNEW);
536 item.addActionListener(this);
541 { // let the user make everything visible again
542 item = new JMenuItem(SHOWALL);
543 item.addActionListener(this);
546 pop.show(this, evt.getX(), evt.getY());
549 item = new JMenuItem(EDITNAME);
550 item.addActionListener(this);
552 item = new JMenuItem(HIDE);
553 item.addActionListener(this);
555 item = new JMenuItem(DELETE);
556 item.addActionListener(this);
560 item = new JMenuItem(SHOWALL);
561 item.addActionListener(this);
564 item = new JMenuItem(OUTPUT_TEXT);
565 item.addActionListener(this);
567 // TODO: annotation object should be typed for autocalculated/derived
569 if (selectedRow < aa.length)
571 if (!aa[selectedRow].autoCalculated)
573 if (aa[selectedRow].graph == AlignmentAnnotation.NO_GRAPH)
575 // display formatting settings for this row.
577 // av and sequencegroup need to implement same interface for
578 item = new JCheckBoxMenuItem(TOGGLE_LABELSCALE,
579 aa[selectedRow].scaleColLabel);
580 item.addActionListener(this);
584 else if (aa[selectedRow].label.indexOf("Consensus") > -1)
587 // av and sequencegroup need to implement same interface for
588 final JCheckBoxMenuItem cbmi = new JCheckBoxMenuItem(
589 "Ignore Gaps In Consensus",
590 (aa[selectedRow].groupRef != null) ? aa[selectedRow].groupRef
591 .getIgnoreGapsConsensus() : ap.av
592 .getIgnoreGapsConsensus());
593 final AlignmentAnnotation aaa = aa[selectedRow];
594 cbmi.addActionListener(new ActionListener()
596 public void actionPerformed(ActionEvent e)
598 if (aaa.groupRef != null)
600 // TODO: pass on reference to ap so the view can be updated.
601 aaa.groupRef.setIgnoreGapsConsensus(cbmi.getState());
602 ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
606 ap.av.setIgnoreGapsConsensus(cbmi.getState(), ap);
611 // av and sequencegroup need to implement same interface for
612 if (aaa.groupRef != null)
614 final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
615 "Show Group Histogram",
616 aa[selectedRow].groupRef.isShowConsensusHistogram());
617 chist.addActionListener(new ActionListener()
619 public void actionPerformed(ActionEvent e)
621 // TODO: pass on reference
627 aaa.groupRef.setShowConsensusHistogram(chist.getState());
629 // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
633 final JCheckBoxMenuItem cprofl = new JCheckBoxMenuItem(
635 aa[selectedRow].groupRef.isShowSequenceLogo());
636 cprofl.addActionListener(new ActionListener()
638 public void actionPerformed(ActionEvent e)
640 // TODO: pass on reference
646 aaa.groupRef.setshowSequenceLogo(cprofl.getState());
648 // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
652 final JCheckBoxMenuItem cproflnorm = new JCheckBoxMenuItem(
653 "Normalise Group Logo",
654 aa[selectedRow].groupRef.isNormaliseSequenceLogo());
655 cproflnorm.addActionListener(new ActionListener()
657 public void actionPerformed(ActionEvent e)
660 // TODO: pass on reference
666 aaa.groupRef.setNormaliseSequenceLogo(cproflnorm.getState());
667 // automatically enable logo display if we're clicked
668 aaa.groupRef.setshowSequenceLogo(true);
670 // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
677 final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
678 "Show Histogram", av.isShowConsensusHistogram());
679 chist.addActionListener(new ActionListener()
681 public void actionPerformed(ActionEvent e)
683 // TODO: pass on reference
689 av.setShowConsensusHistogram(chist.getState());
690 ap.alignFrame.setMenusForViewport();
692 // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
696 final JCheckBoxMenuItem cprof = new JCheckBoxMenuItem(
697 "Show Logo", av.isShowSequenceLogo());
698 cprof.addActionListener(new ActionListener()
700 public void actionPerformed(ActionEvent e)
702 // TODO: pass on reference
708 av.setShowSequenceLogo(cprof.getState());
709 ap.alignFrame.setMenusForViewport();
711 // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
715 final JCheckBoxMenuItem cprofnorm = new JCheckBoxMenuItem(
716 "Normalise Logo", av.isNormaliseSequenceLogo());
717 cprofnorm.addActionListener(new ActionListener()
719 public void actionPerformed(ActionEvent e)
721 // TODO: pass on reference
727 av.setShowSequenceLogo(true);
728 av.setNormaliseSequenceLogo(cprofnorm.getState());
729 ap.alignFrame.setMenusForViewport();
731 // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
736 final JMenuItem consclipbrd = new JMenuItem(COPYCONS_SEQ);
737 consclipbrd.addActionListener(this);
738 pop.add(consclipbrd);
741 pop.show(this, evt.getX(), evt.getY());
745 * do a single sequence copy to jalview and the system clipboard
748 * sequence to be copied to clipboard
750 protected void copy_annotseqtoclipboard(SequenceI sq)
752 SequenceI[] seqs = new SequenceI[]
754 String[] omitHidden = null;
755 SequenceI[] dseqs = new SequenceI[]
756 { sq.getDatasetSequence() };
757 if (dseqs[0] == null)
759 dseqs[0] = new Sequence(sq);
760 dseqs[0].setSequence(jalview.analysis.AlignSeq.extractGaps(
761 jalview.util.Comparison.GapChars, sq.getSequenceAsString()));
763 sq.setDatasetSequence(dseqs[0]);
765 Alignment ds = new Alignment(dseqs);
766 if (av.hasHiddenColumns())
768 omitHidden = av.getColumnSelection().getVisibleSequenceStrings(0,
769 sq.getLength(), seqs);
772 String output = new FormatAdapter().formatSequences("Fasta", seqs,
775 Toolkit.getDefaultToolkit().getSystemClipboard()
776 .setContents(new StringSelection(output), Desktop.instance);
778 Vector hiddenColumns = null;
779 if (av.hasHiddenColumns())
781 hiddenColumns = new Vector();
782 for (int i = 0; i < av.getColumnSelection().getHiddenColumns().size(); i++)
784 int[] region = (int[]) av.getColumnSelection().getHiddenColumns()
787 hiddenColumns.addElement(new int[]
788 { region[0], region[1] });
792 Desktop.jalviewClipboard = new Object[]
793 { seqs, ds, // what is the dataset of a consensus sequence ? need to flag
794 // sequence as special.
804 public void paintComponent(Graphics g)
807 int width = getWidth();
810 width = ap.calculateIdWidth().width + 4;
813 Graphics2D g2 = (Graphics2D) g;
816 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
817 RenderingHints.VALUE_ANTIALIAS_ON);
820 drawComponent(g2, true, width);
825 * Draw the full set of annotation Labels for the alignment at the given cursor
827 * @param g Graphics2D instance (needed for font scaling)
828 * @param width Width for scaling labels
831 public void drawComponent(Graphics g, int width)
833 drawComponent(g, false, width);
836 private final boolean debugRedraw = false;
838 * Draw the full set of annotation Labels for the alignment at the given cursor
840 * @param g Graphics2D instance (needed for font scaling)
841 * @param clip - true indicates that only current visible area needs to be rendered
842 * @param width Width for scaling labels
844 public void drawComponent(Graphics g, boolean clip, int width)
846 if (av.getFont().getSize() < 10)
852 g.setFont(av.getFont());
855 FontMetrics fm = g.getFontMetrics(g.getFont());
856 g.setColor(Color.white);
857 g.fillRect(0, 0, getWidth(), getHeight());
859 g.translate(0, scrollOffset);
860 g.setColor(Color.black);
862 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
863 int fontHeight = g.getFont().getSize();
868 Font baseFont = g.getFont();
869 FontMetrics baseMetrics = fm;
870 int ofontH = fontHeight;
873 int[] visr = (ap!=null && ap.annotationPanel!=null) ? ap.annotationPanel.getVisibleVRange() : null;
874 if (clip && visr!=null){
878 boolean visible = true,before=false,after=false;
881 hasHiddenRows = false;
883 for (int i = 0; i < aa.length; i++)
888 hasHiddenRows = true;
893 if (clip) {if (y<sOffset)
898 System.out.println("before vis: "+i);
902 // don't draw what isn't visible
911 System.out.println("Scroll offset: "+sOffset+" after vis: "+i);
915 // don't draw what isn't visible
918 g.setColor(Color.black);
920 offset = -aa[i].height / 2;
924 offset += fm.getHeight() / 2;
925 offset -= fm.getDescent();
928 offset += fm.getDescent();
930 x = width - fm.stringWidth(aa[i].label) - 3;
932 if (aa[i].graphGroup > -1)
935 // TODO: JAL-1291 revise rendering model so the graphGroup map is computed efficiently for all visible labels
936 for (int gg = 0; gg < aa.length; gg++)
938 if (aa[gg].graphGroup == aa[i].graphGroup)
943 if (groupSize * (fontHeight + 8) < aa[i].height)
945 graphExtras = (aa[i].height - (groupSize * (fontHeight + 8))) / 2;
950 float h = aa[i].height / (float) groupSize, s;
957 fontHeight = -8 + (int) h;
958 s = ((float) fontHeight) / (float) ofontH;
959 Font f = baseFont.deriveFont(AffineTransform
960 .getScaleInstance(s, s));
962 fm = g.getFontMetrics();
963 graphExtras = (aa[i].height - (groupSize * (fontHeight + 8))) / 2;
968 for (int gg = 0; gg < aa.length; gg++)
970 if (aa[gg].graphGroup == aa[i].graphGroup)
972 x = width - fm.stringWidth(aa[gg].label) - 3;
973 g.drawString(aa[gg].label, x, y - graphExtras);
975 if (aa[gg]._linecolour != null)
978 g.setColor(aa[gg]._linecolour);
979 g.drawLine(x, y - graphExtras + 3,
980 x + fm.stringWidth(aa[gg].label), y - graphExtras
984 g.setColor(Color.black);
985 graphExtras += fontHeight + 8;
995 g.drawString(aa[i].label, x, y + offset);
1002 g.drawImage(image, 2, 0 - scrollOffset, this);
1004 else if (dragEvent != null && aa != null)
1006 g.setColor(Color.lightGray);
1007 g.drawString(aa[selectedRow].label, dragEvent.getX(),
1008 dragEvent.getY() - scrollOffset);
1011 if (!av.wrapAlignment && ((aa == null) || (aa.length < 1)))
1013 g.drawString(MessageManager.getString("label.right_click"), 2, 8);
1014 g.drawString(MessageManager.getString("label.to_add_annotation"), 2, 18);