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.Color;
24 import java.awt.Cursor;
25 import java.awt.Dimension;
27 import java.awt.FontMetrics;
28 import java.awt.Graphics;
29 import java.awt.Graphics2D;
30 import java.awt.RenderingHints;
31 import java.awt.Toolkit;
32 import java.awt.datatransfer.StringSelection;
33 import java.awt.event.ActionEvent;
34 import java.awt.event.ActionListener;
35 import java.awt.event.MouseEvent;
36 import java.awt.event.MouseListener;
37 import java.awt.event.MouseMotionListener;
38 import java.awt.geom.AffineTransform;
39 import java.util.Arrays;
40 import java.util.Collections;
41 import java.util.Iterator;
42 import java.util.Locale;
44 import javax.swing.JCheckBoxMenuItem;
45 import javax.swing.JMenuItem;
46 import javax.swing.JPanel;
47 import javax.swing.JPopupMenu;
48 import javax.swing.SwingUtilities;
49 import javax.swing.ToolTipManager;
51 import jalview.analysis.AlignSeq;
52 import jalview.analysis.AlignmentUtils;
53 import jalview.bin.Cache;
54 import jalview.bin.Jalview;
55 import jalview.datamodel.Alignment;
56 import jalview.datamodel.AlignmentAnnotation;
57 import jalview.datamodel.Annotation;
58 import jalview.datamodel.ContactMatrixI;
59 import jalview.datamodel.GroupSet;
60 import jalview.datamodel.HiddenColumns;
61 import jalview.datamodel.Sequence;
62 import jalview.datamodel.SequenceGroup;
63 import jalview.datamodel.SequenceI;
64 import jalview.io.FileFormat;
65 import jalview.io.FormatAdapter;
66 import jalview.util.Comparison;
67 import jalview.util.MessageManager;
68 import jalview.util.Platform;
71 * The panel that holds the labels for alignment annotations, providing
72 * tooltips, context menus, drag to reorder rows, and drag to adjust panel
75 public class AnnotationLabels extends JPanel
76 implements MouseListener, MouseMotionListener, ActionListener
78 private static final String HTML_END_TAG = "</html>";
80 private static final String HTML_START_TAG = "<html>";
83 * width in pixels within which height adjuster arrows are shown and active
85 private static final int HEIGHT_ADJUSTER_WIDTH = 50;
88 * height in pixels for allowing height adjuster to be active
90 private static int HEIGHT_ADJUSTER_HEIGHT = 10;
92 private static final Font font = new Font("Arial", Font.PLAIN, 11);
94 private static final String TOGGLE_LABELSCALE = MessageManager
95 .getString("label.scale_label_to_column");
97 private static final String ADDNEW = MessageManager
98 .getString("label.add_new_row");
100 private static final String EDITNAME = MessageManager
101 .getString("label.edit_label_description");
103 private static final String HIDE = MessageManager
104 .getString("label.hide_row");
106 private static final String DELETE = MessageManager
107 .getString("label.delete_row");
109 private static final String SHOWALL = MessageManager
110 .getString("label.show_all_hidden_rows");
112 private static final String OUTPUT_TEXT = MessageManager
113 .getString("label.export_annotation");
115 private static final String COPYCONS_SEQ = MessageManager
116 .getString("label.copy_consensus_sequence");
118 private static final String ADJUST_ANNOTATION_LABELS_WIDTH_PREF = "ADJUST_ANNOTATION_LABELS_WIDTH";
120 private final boolean debugRedraw = false;
122 private AlignmentPanel ap;
126 private MouseEvent dragEvent;
130 private int selectedRow;
132 private int scrollOffset = 0;
134 private boolean hasHiddenRows;
136 private boolean resizePanel = false;
139 * Creates a new AnnotationLabels object
143 public AnnotationLabels(AlignmentPanel ap)
147 ToolTipManager.sharedInstance().registerComponent(this);
149 addMouseListener(this);
150 addMouseMotionListener(this);
151 addMouseWheelListener(ap.getAnnotationPanel());
154 public AnnotationLabels(AlignViewport av)
165 public void setScrollOffset(int y)
172 * sets selectedRow to -2 if no annotation preset, -1 if no visible row is at
176 * coordinate position to search for a row
178 void getSelectedRow(int y)
181 AlignmentAnnotation[] aa = ap.av.getAlignment()
182 .getAlignmentAnnotation();
186 for (int i = 0; i < aa.length; i++)
194 height += aa[i].height;
213 public void actionPerformed(ActionEvent evt)
215 AlignmentAnnotation[] aa = ap.av.getAlignment()
216 .getAlignmentAnnotation();
218 String action = evt.getActionCommand();
219 if (ADDNEW.equals(action))
222 * non-returning dialog
224 AlignmentAnnotation newAnnotation = new AlignmentAnnotation(null,
225 null, new Annotation[ap.av.getAlignment().getWidth()]);
226 editLabelDescription(newAnnotation, true);
228 else if (EDITNAME.equals(action))
231 * non-returning dialog
233 editLabelDescription(aa[selectedRow], false);
235 else if (HIDE.equals(action))
237 aa[selectedRow].visible = false;
239 else if (DELETE.equals(action))
241 ap.av.getAlignment().deleteAnnotation(aa[selectedRow]);
242 ap.av.getCalcManager().removeWorkerForAnnotation(aa[selectedRow]);
244 else if (SHOWALL.equals(action))
246 for (int i = 0; i < aa.length; i++)
248 if (!aa[i].visible && aa[i].annotations != null)
250 aa[i].visible = true;
254 else if (OUTPUT_TEXT.equals(action))
256 new AnnotationExporter(ap).exportAnnotation(aa[selectedRow]);
258 else if (COPYCONS_SEQ.equals(action))
260 SequenceI cons = null;
261 if (aa[selectedRow].groupRef != null)
263 cons = aa[selectedRow].groupRef.getConsensusSeq();
267 cons = av.getConsensusSeq();
271 copy_annotseqtoclipboard(cons);
274 else if (TOGGLE_LABELSCALE.equals(action))
276 aa[selectedRow].scaleColLabel = !aa[selectedRow].scaleColLabel;
283 * Shows a dialog where the annotation name and description may be edited. If
284 * parameter addNew is true, then on confirmation, a new AlignmentAnnotation
285 * is added, else an existing annotation is updated.
290 void editLabelDescription(AlignmentAnnotation annotation, boolean addNew)
292 String name = MessageManager.getString("label.annotation_name");
293 String description = MessageManager
294 .getString("label.annotation_description");
295 String title = MessageManager
296 .getString("label.edit_annotation_name_description");
297 EditNameDialog dialog = new EditNameDialog(annotation.label,
298 annotation.description, name, description);
300 dialog.showDialog(ap.alignFrame, title, () -> {
301 annotation.label = dialog.getName();
302 String text = dialog.getDescription();
303 if (text != null && text.length() == 0)
307 annotation.description = text;
310 ap.av.getAlignment().addAnnotation(annotation);
311 ap.av.getAlignment().setAnnotationIndex(annotation, 0);
318 public void mousePressed(MouseEvent evt)
320 getSelectedRow(evt.getY() - getScrollOffset());
322 if (evt.isPopupTrigger())
329 * Build and show the Pop-up menu at the right-click mouse position
333 void showPopupMenu(MouseEvent evt)
336 final AlignmentAnnotation[] aa = ap.av.getAlignment()
337 .getAlignmentAnnotation();
339 JPopupMenu pop = new JPopupMenu(
340 MessageManager.getString("label.annotations"));
341 JMenuItem item = new JMenuItem(ADDNEW);
342 item.addActionListener(this);
347 { // let the user make everything visible again
348 item = new JMenuItem(SHOWALL);
349 item.addActionListener(this);
352 pop.show(this, evt.getX(), evt.getY());
355 item = new JMenuItem(EDITNAME);
356 item.addActionListener(this);
358 item = new JMenuItem(HIDE);
359 item.addActionListener(this);
361 // JAL-1264 hide all sequence-specific annotations of this type
362 if (selectedRow < aa.length)
364 if (aa[selectedRow].sequenceRef != null)
366 final String label = aa[selectedRow].label;
367 JMenuItem hideType = new JMenuItem();
368 String text = MessageManager.getString("label.hide_all") + " "
370 hideType.setText(text);
371 hideType.addActionListener(new ActionListener()
374 public void actionPerformed(ActionEvent e)
376 AlignmentUtils.showOrHideSequenceAnnotations(
377 ap.av.getAlignment(), Collections.singleton(label),
385 item = new JMenuItem(DELETE);
386 item.addActionListener(this);
390 item = new JMenuItem(SHOWALL);
391 item.addActionListener(this);
394 item = new JMenuItem(OUTPUT_TEXT);
395 item.addActionListener(this);
397 // TODO: annotation object should be typed for autocalculated/derived
399 if (selectedRow < aa.length)
401 final String label = aa[selectedRow].label;
402 if (!aa[selectedRow].autoCalculated)
404 if (aa[selectedRow].graph == AlignmentAnnotation.NO_GRAPH)
406 // display formatting settings for this row.
408 // av and sequencegroup need to implement same interface for
409 item = new JCheckBoxMenuItem(TOGGLE_LABELSCALE,
410 aa[selectedRow].scaleColLabel);
411 item.addActionListener(this);
415 else if (label.indexOf("Consensus") > -1)
417 addConsensusMenuOptions(ap, aa[selectedRow], pop);
419 final JMenuItem consclipbrd = new JMenuItem(COPYCONS_SEQ);
420 consclipbrd.addActionListener(this);
421 pop.add(consclipbrd);
424 addColourOrFilterByOptions(ap, aa[selectedRow], pop);
426 if (aa[selectedRow].graph == AlignmentAnnotation.CONTACT_MAP)
428 addContactMatrixOptions(ap, aa[selectedRow], pop);
429 // Set/adjust threshold for grouping ?
430 // colour alignment by this [type]
431 // select/hide columns by this row
436 pop.show(this, evt.getX(), evt.getY());
439 static void addColourOrFilterByOptions(final AlignmentPanel ap,
440 final AlignmentAnnotation alignmentAnnotation,
441 final JPopupMenu pop)
444 item = new JMenuItem(
445 MessageManager.getString("label.colour_by_annotation"));
446 item.addActionListener(new ActionListener()
450 public void actionPerformed(ActionEvent e)
452 AnnotationColourChooser.displayFor(ap.av, ap, alignmentAnnotation,
457 if (alignmentAnnotation.sequenceRef != null)
459 item = new JMenuItem(
460 MessageManager.getString("label.colour_by_annotation") + " ("
461 + MessageManager.getString("label.per_seq") + ")");
462 item.addActionListener(new ActionListener()
465 public void actionPerformed(ActionEvent e)
467 AnnotationColourChooser.displayFor(ap.av, ap, alignmentAnnotation,
473 item = new JMenuItem(
474 MessageManager.getString("action.select_by_annotation"));
475 item.addActionListener(new ActionListener()
479 public void actionPerformed(ActionEvent e)
481 AnnotationColumnChooser.displayFor(ap.av, ap, alignmentAnnotation);
487 static void addContactMatrixOptions(final AlignmentPanel ap,
488 final AlignmentAnnotation alignmentAnnotation,
489 final JPopupMenu pop)
492 final ContactMatrixI cm = ap.av.getContactMatrix(alignmentAnnotation);
500 JCheckBoxMenuItem chitem = new JCheckBoxMenuItem(
501 MessageManager.getString("action.show_groups_on_matrix"));
502 chitem.setToolTipText(MessageManager
503 .getString("action.show_groups_on_matrix_tooltip"));
504 boolean showGroups = alignmentAnnotation
505 .isShowGroupsForContactMatrix();
506 final AlignmentAnnotation sel_row = alignmentAnnotation;
507 chitem.setState(showGroups);
508 chitem.addActionListener(new ActionListener()
512 public void actionPerformed(ActionEvent e)
514 sel_row.setShowGroupsForContactMatrix(chitem.getState());
515 ap.getAnnotationPanel()
516 .paint(ap.getAnnotationPanel().getGraphics());
523 item = new JMenuItem(
524 MessageManager.getString("action.show_tree_for_matrix"));
525 item.setToolTipText(MessageManager
526 .getString("action.show_tree_for_matrix_tooltip"));
527 item.addActionListener(new ActionListener()
531 public void actionPerformed(ActionEvent e)
534 ap.alignFrame.showContactMapTree(alignmentAnnotation, cm);
542 item = new JMenuItem(
543 MessageManager.getString("action.cluster_matrix"));
545 MessageManager.getString("action.cluster_matrix_tooltip"));
546 item.addActionListener(new ActionListener()
549 public void actionPerformed(ActionEvent e)
551 new Thread(new Runnable()
557 ap.alignFrame.setProgressBar(
558 MessageManager.formatMessage(
559 "action.clustering_matrix_for",
560 cm.getAnnotDescr(), 5f),
561 progBar = System.currentTimeMillis());
562 cm.setGroupSet(GroupSet.makeGroups(cm, 5f, true));
563 ap.alignFrame.showContactMapTree(alignmentAnnotation, cm);
564 ap.alignFrame.setProgressBar(null, progBar);
575 * A helper method that adds menu options for calculation and visualisation of
576 * group and/or alignment consensus annotation to a popup menu. This is
577 * designed to be reusable for either unwrapped mode (popup menu is shown on
578 * component AnnotationLabels), or wrapped mode (popup menu is shown on
579 * IdPanel when the mouse is over an annotation label).
585 static void addConsensusMenuOptions(AlignmentPanel ap,
586 AlignmentAnnotation ann, JPopupMenu pop)
590 final JCheckBoxMenuItem cbmi = new JCheckBoxMenuItem(
591 MessageManager.getString("label.ignore_gaps_consensus"),
592 (ann.groupRef != null) ? ann.groupRef.getIgnoreGapsConsensus()
593 : ap.av.isIgnoreGapsConsensus());
594 final AlignmentAnnotation aaa = ann;
595 cbmi.addActionListener(new ActionListener()
598 public void actionPerformed(ActionEvent e)
600 if (aaa.groupRef != null)
602 aaa.groupRef.setIgnoreGapsConsensus(cbmi.getState());
603 ap.getAnnotationPanel()
604 .paint(ap.getAnnotationPanel().getGraphics());
608 ap.av.setIgnoreGapsConsensus(cbmi.getState(), ap);
610 ap.alignmentChanged();
615 if (aaa.groupRef != null)
618 * group consensus options
620 final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
621 MessageManager.getString("label.show_group_histogram"),
622 ann.groupRef.isShowConsensusHistogram());
623 chist.addActionListener(new ActionListener()
626 public void actionPerformed(ActionEvent e)
628 aaa.groupRef.setShowConsensusHistogram(chist.getState());
633 final JCheckBoxMenuItem cprofl = new JCheckBoxMenuItem(
634 MessageManager.getString("label.show_group_logo"),
635 ann.groupRef.isShowSequenceLogo());
636 cprofl.addActionListener(new ActionListener()
639 public void actionPerformed(ActionEvent e)
641 aaa.groupRef.setshowSequenceLogo(cprofl.getState());
646 final JCheckBoxMenuItem cproflnorm = new JCheckBoxMenuItem(
647 MessageManager.getString("label.normalise_group_logo"),
648 ann.groupRef.isNormaliseSequenceLogo());
649 cproflnorm.addActionListener(new ActionListener()
652 public void actionPerformed(ActionEvent e)
654 aaa.groupRef.setNormaliseSequenceLogo(cproflnorm.getState());
655 // automatically enable logo display if we're clicked
656 aaa.groupRef.setshowSequenceLogo(true);
665 * alignment consensus options
667 final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
668 MessageManager.getString("label.show_histogram"),
669 ap.av.isShowConsensusHistogram());
670 chist.addActionListener(new ActionListener()
673 public void actionPerformed(ActionEvent e)
675 ap.av.setShowConsensusHistogram(chist.getState());
676 ap.alignFrame.setMenusForViewport();
681 final JCheckBoxMenuItem cprof = new JCheckBoxMenuItem(
682 MessageManager.getString("label.show_logo"),
683 ap.av.isShowSequenceLogo());
684 cprof.addActionListener(new ActionListener()
687 public void actionPerformed(ActionEvent e)
689 ap.av.setShowSequenceLogo(cprof.getState());
690 ap.alignFrame.setMenusForViewport();
695 final JCheckBoxMenuItem cprofnorm = new JCheckBoxMenuItem(
696 MessageManager.getString("label.normalise_logo"),
697 ap.av.isNormaliseSequenceLogo());
698 cprofnorm.addActionListener(new ActionListener()
701 public void actionPerformed(ActionEvent e)
703 ap.av.setShowSequenceLogo(true);
704 ap.av.setNormaliseSequenceLogo(cprofnorm.getState());
705 ap.alignFrame.setMenusForViewport();
714 * Reorders annotation rows after a drag of a label
719 public void mouseReleased(MouseEvent evt)
721 if (evt.isPopupTrigger())
727 int start = selectedRow;
728 getSelectedRow(evt.getY() - getScrollOffset());
729 int end = selectedRow;
732 * if dragging to resize instead, start == end
736 // Swap these annotations
737 AlignmentAnnotation startAA = ap.av.getAlignment()
738 .getAlignmentAnnotation()[start];
741 end = ap.av.getAlignment().getAlignmentAnnotation().length - 1;
743 AlignmentAnnotation endAA = ap.av.getAlignment()
744 .getAlignmentAnnotation()[end];
746 ap.av.getAlignment().getAlignmentAnnotation()[end] = startAA;
747 ap.av.getAlignment().getAlignmentAnnotation()[start] = endAA;
753 ap.getAnnotationPanel().repaint();
757 * Removes the height adjuster image on leaving the panel, unless currently
761 public void mouseExited(MouseEvent evt)
763 if (resizePanel && dragEvent == null)
771 * A mouse drag may be either an adjustment of the panel height (if flag
772 * resizePanel is set on), or a reordering of the annotation rows. The former
773 * is dealt with by this method, the latter in mouseReleased.
778 public void mouseDragged(MouseEvent evt)
784 Dimension d = ap.annotationScroller.getPreferredSize();
785 int dif = evt.getY() - oldY;
787 dif /= ap.av.getCharHeight();
788 dif *= ap.av.getCharHeight();
790 if ((d.height - dif) > 20)
792 ap.annotationScroller
793 .setPreferredSize(new Dimension(d.width, d.height - dif));
794 d = ap.annotationSpaceFillerHolder.getPreferredSize();
795 ap.annotationSpaceFillerHolder
796 .setPreferredSize(new Dimension(d.width, d.height - dif));
797 ap.paintAlignment(true, false);
809 * Updates the tooltip as the mouse moves over the labels
814 public void mouseMoved(MouseEvent evt)
816 showOrHideAdjuster(evt);
818 getSelectedRow(evt.getY() - getScrollOffset());
820 if (selectedRow > -1 && ap.av.getAlignment()
821 .getAlignmentAnnotation().length > selectedRow)
823 AlignmentAnnotation[] anns = ap.av.getAlignment()
824 .getAlignmentAnnotation();
825 AlignmentAnnotation aa = anns[selectedRow];
827 String desc = getTooltip(aa);
828 this.setToolTipText(desc);
829 String msg = getStatusMessage(aa, anns);
830 ap.alignFrame.setStatus(msg);
835 * Constructs suitable text to show in the status bar when over an annotation
836 * label, containing the associated sequence name (if any), and the annotation
837 * labels (or all labels for a graph group annotation)
843 static String getStatusMessage(AlignmentAnnotation aa,
844 AlignmentAnnotation[] anns)
851 StringBuilder msg = new StringBuilder(32);
852 if (aa.sequenceRef != null)
854 msg.append(aa.sequenceRef.getName()).append(" : ");
857 if (aa.graphGroup == -1)
859 msg.append(aa.label);
861 else if (anns != null)
863 boolean first = true;
864 for (int i = anns.length - 1; i >= 0; i--)
866 if (anns[i].graphGroup == aa.graphGroup)
872 msg.append(anns[i].label);
878 return msg.toString();
882 * Answers a tooltip, formatted as html, containing the annotation description
883 * (prefixed by associated sequence id if applicable), and the annotation
884 * (non-positional) score if it has one. Answers null if neither description
885 * nor score is found.
890 static String getTooltip(AlignmentAnnotation aa)
896 StringBuilder tooltip = new StringBuilder();
897 if (aa.description != null && !aa.description.equals("New description"))
899 // TODO: we could refactor and merge this code with the code in
900 // jalview.gui.SeqPanel.mouseMoved(..) that formats sequence feature
902 String desc = aa.getDescription(true).trim();
903 if (!desc.toLowerCase(Locale.ROOT).startsWith(HTML_START_TAG))
905 tooltip.append(HTML_START_TAG);
906 desc = desc.replace("<", "<");
908 else if (desc.toLowerCase(Locale.ROOT).endsWith(HTML_END_TAG))
910 desc = desc.substring(0, desc.length() - HTML_END_TAG.length());
912 tooltip.append(desc);
916 // begin the tooltip's html fragment
917 tooltip.append(HTML_START_TAG);
921 if (tooltip.length() > HTML_START_TAG.length())
923 tooltip.append("<br/>");
925 // TODO: limit precision of score to avoid noise from imprecise
927 // (64.7 becomes 64.7+/some tiny value).
928 tooltip.append(" Score: ").append(String.valueOf(aa.score));
931 if (tooltip.length() > HTML_START_TAG.length())
933 return tooltip.append(HTML_END_TAG).toString();
937 * nothing in the tooltip (except "<html>")
943 * Shows the height adjuster image if the mouse moves into the top left
944 * region, or hides it if the mouse leaves the regio
948 protected void showOrHideAdjuster(MouseEvent evt)
950 boolean was = resizePanel;
951 resizePanel = evt.getY() < HEIGHT_ADJUSTER_HEIGHT
952 && evt.getX() < HEIGHT_ADJUSTER_WIDTH;
954 if (resizePanel != was)
957 .getPredefinedCursor(resizePanel ? Cursor.S_RESIZE_CURSOR
958 : Cursor.DEFAULT_CURSOR));
964 public void mouseClicked(MouseEvent evt)
966 final AlignmentAnnotation[] aa = ap.av.getAlignment()
967 .getAlignmentAnnotation();
968 if (!evt.isPopupTrigger() && SwingUtilities.isLeftMouseButton(evt))
970 if (selectedRow > -1 && selectedRow < aa.length)
972 if (aa[selectedRow].groupRef != null)
974 if (evt.getClickCount() >= 2)
976 // todo: make the ap scroll to the selection - not necessary, first
977 // click highlights/scrolls, second selects
978 ap.getSeqPanel().ap.getIdPanel().highlightSearchResults(null);
980 SequenceGroup sg = ap.av.getSelectionGroup();
981 if (sg == null || sg == aa[selectedRow].groupRef
982 || !(Platform.isControlDown(evt) || evt.isShiftDown()))
984 if (Platform.isControlDown(evt) || evt.isShiftDown())
986 // clone a new selection group from the associated group
987 ap.av.setSelectionGroup(
988 new SequenceGroup(aa[selectedRow].groupRef));
992 // set selection to the associated group so it can be edited
993 ap.av.setSelectionGroup(aa[selectedRow].groupRef);
998 // modify current selection with associated group
999 int remainToAdd = aa[selectedRow].groupRef.getSize();
1000 for (SequenceI sgs : aa[selectedRow].groupRef.getSequences())
1002 if (jalview.util.Platform.isControlDown(evt))
1004 sg.addOrRemove(sgs, --remainToAdd == 0);
1008 // notionally, we should also add intermediate sequences from
1009 // last added sequence ?
1010 sg.addSequence(sgs, --remainToAdd == 0);
1015 ap.paintAlignment(false, false);
1016 PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
1017 ap.av.sendSelection();
1021 ap.getSeqPanel().ap.getIdPanel().highlightSearchResults(
1022 aa[selectedRow].groupRef.getSequences(null));
1026 else if (aa[selectedRow].sequenceRef != null)
1028 if (evt.getClickCount() == 1)
1030 ap.getSeqPanel().ap.getIdPanel()
1031 .highlightSearchResults(Arrays.asList(new SequenceI[]
1032 { aa[selectedRow].sequenceRef }));
1034 else if (evt.getClickCount() >= 2)
1036 ap.getSeqPanel().ap.getIdPanel().highlightSearchResults(null);
1037 SequenceGroup sg = ap.av.getSelectionGroup();
1040 // we make a copy rather than edit the current selection if no
1041 // modifiers pressed
1042 // see Enhancement JAL-1557
1043 if (!(Platform.isControlDown(evt) || evt.isShiftDown()))
1045 sg = new SequenceGroup(sg);
1047 sg.addSequence(aa[selectedRow].sequenceRef, false);
1051 if (Platform.isControlDown(evt))
1053 sg.addOrRemove(aa[selectedRow].sequenceRef, true);
1057 // notionally, we should also add intermediate sequences from
1058 // last added sequence ?
1059 sg.addSequence(aa[selectedRow].sequenceRef, true);
1065 sg = new SequenceGroup();
1067 sg.setEndRes(ap.av.getAlignment().getWidth() - 1);
1068 sg.addSequence(aa[selectedRow].sequenceRef, false);
1070 ap.av.setSelectionGroup(sg);
1071 ap.paintAlignment(false, false);
1072 PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
1073 ap.av.sendSelection();
1083 * do a single sequence copy to jalview and the system clipboard
1086 * sequence to be copied to clipboard
1088 protected void copy_annotseqtoclipboard(SequenceI sq)
1090 SequenceI[] seqs = new SequenceI[] { sq };
1091 String[] omitHidden = null;
1092 SequenceI[] dseqs = new SequenceI[] { sq.getDatasetSequence() };
1093 if (dseqs[0] == null)
1095 dseqs[0] = new Sequence(sq);
1096 dseqs[0].setSequence(AlignSeq.extractGaps(Comparison.GapChars,
1097 sq.getSequenceAsString()));
1099 sq.setDatasetSequence(dseqs[0]);
1101 Alignment ds = new Alignment(dseqs);
1102 if (av.hasHiddenColumns())
1104 Iterator<int[]> it = av.getAlignment().getHiddenColumns()
1105 .getVisContigsIterator(0, sq.getLength(), false);
1106 omitHidden = new String[] { sq.getSequenceStringFromIterator(it) };
1109 int[] alignmentStartEnd = new int[] { 0, ds.getWidth() - 1 };
1110 if (av.hasHiddenColumns())
1112 alignmentStartEnd = av.getAlignment().getHiddenColumns()
1113 .getVisibleStartAndEndIndex(av.getAlignment().getWidth());
1116 String output = new FormatAdapter().formatSequences(FileFormat.Fasta,
1117 seqs, omitHidden, alignmentStartEnd);
1119 Toolkit.getDefaultToolkit().getSystemClipboard()
1120 .setContents(new StringSelection(output), Desktop.instance);
1122 HiddenColumns hiddenColumns = null;
1124 if (av.hasHiddenColumns())
1126 hiddenColumns = new HiddenColumns(
1127 av.getAlignment().getHiddenColumns());
1130 Desktop.jalviewClipboard = new Object[] { seqs, ds, // what is the dataset
1132 // sequence ? need to
1134 // sequence as special.
1145 public void paintComponent(Graphics g)
1148 int width = getWidth();
1151 width = ap.calculateIdWidth().width;
1154 Graphics2D g2 = (Graphics2D) g;
1157 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
1158 RenderingHints.VALUE_ANTIALIAS_ON);
1161 drawComponent(g2, true, width);
1166 * Draw the full set of annotation Labels for the alignment at the given
1170 * Graphics2D instance (needed for font scaling)
1172 * Width for scaling labels
1175 public void drawComponent(Graphics g, int width)
1177 drawComponent(g, false, width);
1181 * Draw the full set of annotation Labels for the alignment at the given
1185 * Graphics2D instance (needed for font scaling)
1187 * - true indicates that only current visible area needs to be
1190 * Width for scaling labels
1192 public void drawComponent(Graphics g, boolean clip, int givenWidth)
1194 int width = givenWidth;
1195 IdwidthAdjuster iwa = ap.idwidthAdjuster;
1196 if ((Cache.getDefault(ADJUST_ANNOTATION_LABELS_WIDTH_PREF, true)
1197 || Jalview.isHeadlessMode()))
1199 Graphics2D g2d = (Graphics2D) g;
1200 Graphics dummy = g2d.create();
1201 int annotationIdWidth = drawLabels(dummy, clip, width, false);
1202 Dimension d = ap.calculateDefaultAlignmentIdWidth();
1203 int alignmentIdWidth = d.width;
1204 if (!iwa.manuallyAdjusted())
1206 // If no manual adjustment to ID column with has been made then adjust
1207 // width match widest of alignment or annotation id widths
1208 width = Math.max(alignmentIdWidth, annotationIdWidth);
1210 else if (annotationIdWidth > givenWidth
1211 && annotationIdWidth > alignmentIdWidth)
1213 // otherwise if the annotation id width has become larger than the
1214 // current id width, increase
1215 width = annotationIdWidth;
1217 // set the width if it's changed
1218 if (width != ap.av.getIdWidth())
1220 iwa.setWidth(width);
1221 ap.validateAnnotationDimensions(false);
1224 drawLabels(g, clip, width, true);
1228 * Render the full set of annotation Labels for the alignment at the given
1229 * cursor. If actuallyDraw is false then no actual drawing will occur, but the
1230 * widest label width will be returned.
1233 * Graphics2D instance (needed for font scaling)
1235 * - true indicates that only current visible area needs to be
1238 * Width for scaling labels
1240 public int drawLabels(Graphics g, boolean clip, int width,
1241 boolean actuallyDraw)
1243 int actualWidth = 0;
1244 if (av.getFont().getSize() < 10)
1250 g.setFont(av.getFont());
1253 FontMetrics fm = g.getFontMetrics(g.getFont());
1256 g.setColor(Color.white);
1257 g.fillRect(0, 0, getWidth(), getHeight());
1262 g.translate(0, getScrollOffset());
1263 g.setColor(Color.black);
1265 SequenceI lastSeqRef = null;
1266 String lastLabel = null;
1267 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
1268 int fontHeight = g.getFont().getSize();
1271 int graphExtras = 0;
1273 Font baseFont = g.getFont();
1274 FontMetrics baseMetrics = fm;
1275 int ofontH = fontHeight;
1278 int[] visr = (ap != null && ap.getAnnotationPanel() != null)
1279 ? ap.getAnnotationPanel().getVisibleVRange()
1281 if (clip && visr != null)
1284 visHeight = visr[1];
1286 boolean visible = true, before = false, after = false;
1289 hasHiddenRows = false;
1292 for (int i = 0; i < aa.length; i++)
1297 hasHiddenRows = true;
1301 // look ahead to next annotation
1302 for (nexAA = i + 1; nexAA < aa.length
1303 && !aa[nexAA].visible; nexAA++)
1314 System.out.println("before vis: " + i);
1318 // don't draw what isn't visible
1321 if (olY > visHeight)
1329 "Scroll offset: " + sOffset + " after vis: " + i);
1333 // don't draw what isn't visible
1339 g.setColor(Color.black);
1341 offset = -aa[i].height / 2;
1345 offset += fm.getHeight() / 2;
1346 offset -= fm.getDescent();
1350 offset += fm.getDescent();
1352 String label = aa[i].label;
1353 boolean vertBar = false;
1354 if ((lastLabel != null && lastLabel.equals(label)))
1356 label = aa[i].description;
1360 if (nexAA < aa.length && label.equals(aa[nexAA].label)) // &&
1361 // aa[nexY].sequenceRef==aa[i].sequenceRef)
1364 // next label is the same as this label
1365 label = aa[i].description;
1372 if (aa[i].sequenceRef != null)
1374 if (aa[i].sequenceRef != lastSeqRef)
1376 label = aa[i].sequenceRef.getName() + " " + label;
1377 // TODO record relationship between sequence and this annotation and
1386 int labelWidth = fm.stringWidth(label) + 3;
1387 x = width - labelWidth;
1389 if (aa[i].graphGroup > -1)
1392 // TODO: JAL-1291 revise rendering model so the graphGroup map is
1393 // computed efficiently for all visible labels
1394 for (int gg = 0; gg < aa.length; gg++)
1396 if (aa[gg].graphGroup == aa[i].graphGroup)
1401 if (groupSize * (fontHeight + 8) < aa[i].height)
1403 graphExtras = (aa[i].height - (groupSize * (fontHeight + 8)))
1408 // scale font to fit
1409 float h = aa[i].height / (float) groupSize, s;
1416 fontHeight = -8 + (int) h;
1417 s = ((float) fontHeight) / (float) ofontH;
1419 .deriveFont(AffineTransform.getScaleInstance(s, s));
1421 fm = g.getFontMetrics();
1422 graphExtras = (aa[i].height - (groupSize * (fontHeight + 8)))
1428 for (int gg = 0; gg < aa.length; gg++)
1430 if (aa[gg].graphGroup == aa[i].graphGroup)
1432 labelWidth = fm.stringWidth(aa[gg].label) + 3;
1433 x = width - labelWidth;
1436 g.drawString(aa[gg].label, x, y - graphExtras);
1438 if (aa[gg]._linecolour != null)
1441 g.setColor(aa[gg]._linecolour);
1442 g.drawLine(x, y - graphExtras + 3,
1443 x + fm.stringWidth(aa[gg].label),
1444 y - graphExtras + 3);
1447 g.setColor(Color.black);
1449 graphExtras += fontHeight + 8;
1453 g.setFont(baseFont);
1455 fontHeight = ofontH;
1463 g.drawLine(width - 3, y + offset - fontHeight, width - 3,
1464 (int) (y - 1.5 * aa[i].height - offset - fontHeight));
1465 // g.drawLine(20, y + offset, x - 20, y + offset);
1468 g.drawString(label, x, y + offset);
1471 lastSeqRef = aa[i].sequenceRef;
1473 if (labelWidth > actualWidth)
1475 actualWidth = labelWidth;
1480 if (!resizePanel && dragEvent != null && aa != null)
1484 g.setColor(Color.lightGray);
1486 (aa[selectedRow].sequenceRef == null ? ""
1487 : aa[selectedRow].sequenceRef.getName())
1488 + aa[selectedRow].label,
1489 dragEvent.getX(), dragEvent.getY() - getScrollOffset());
1493 if (!av.getWrapAlignment() && ((aa == null) || (aa.length < 1)))
1497 g.drawString(MessageManager.getString("label.right_click"), 2, 8);
1498 g.drawString(MessageManager.getString("label.to_add_annotation"), 2,
1506 public int getScrollOffset()
1508 return scrollOffset;
1512 public void mouseEntered(MouseEvent e)