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.Canvas;
24 import java.awt.Color;
25 import java.awt.Cursor;
26 import java.awt.Dimension;
28 import java.awt.FontMetrics;
29 import java.awt.Graphics;
30 import java.awt.Graphics2D;
31 import java.awt.RenderingHints;
32 import java.awt.Toolkit;
33 import java.awt.datatransfer.StringSelection;
34 import java.awt.event.ActionEvent;
35 import java.awt.event.ActionListener;
36 import java.awt.event.MouseEvent;
37 import java.awt.event.MouseListener;
38 import java.awt.event.MouseMotionListener;
39 import java.awt.geom.AffineTransform;
40 import java.util.Arrays;
41 import java.util.Collections;
42 import java.util.Iterator;
43 import java.util.Locale;
45 import javax.swing.JCheckBoxMenuItem;
46 import javax.swing.JMenuItem;
47 import javax.swing.JPanel;
48 import javax.swing.JPopupMenu;
49 import javax.swing.SwingUtilities;
50 import javax.swing.ToolTipManager;
52 import jalview.analysis.AlignSeq;
53 import jalview.analysis.AlignmentUtils;
54 import jalview.bin.Cache;
55 import jalview.bin.Jalview;
56 import jalview.datamodel.Alignment;
57 import jalview.datamodel.AlignmentAnnotation;
58 import jalview.datamodel.Annotation;
59 import jalview.datamodel.ContactMatrixI;
60 import jalview.datamodel.GroupSet;
61 import jalview.datamodel.HiddenColumns;
62 import jalview.datamodel.Sequence;
63 import jalview.datamodel.SequenceGroup;
64 import jalview.datamodel.SequenceI;
65 import jalview.io.FileFormat;
66 import jalview.io.FormatAdapter;
67 import jalview.util.Comparison;
68 import jalview.util.MessageManager;
69 import jalview.util.Platform;
72 * The panel that holds the labels for alignment annotations, providing
73 * tooltips, context menus, drag to reorder rows, and drag to adjust panel
76 public class AnnotationLabels extends JPanel
77 implements MouseListener, MouseMotionListener, ActionListener
79 private static final String HTML_END_TAG = "</html>";
81 private static final String HTML_START_TAG = "<html>";
84 * width in pixels within which height adjuster arrows are shown and active
86 private static final int HEIGHT_ADJUSTER_WIDTH = 50;
89 * height in pixels for allowing height adjuster to be active
91 private static int HEIGHT_ADJUSTER_HEIGHT = 10;
93 private static final Font font = new Font("Arial", Font.PLAIN, 11);
95 private static final String TOGGLE_LABELSCALE = MessageManager
96 .getString("label.scale_label_to_column");
98 private static final String ADDNEW = MessageManager
99 .getString("label.add_new_row");
101 private static final String EDITNAME = MessageManager
102 .getString("label.edit_label_description");
104 private static final String HIDE = MessageManager
105 .getString("label.hide_row");
107 private static final String DELETE = MessageManager
108 .getString("label.delete_row");
110 private static final String SHOWALL = MessageManager
111 .getString("label.show_all_hidden_rows");
113 private static final String OUTPUT_TEXT = MessageManager
114 .getString("label.export_annotation");
116 private static final String COPYCONS_SEQ = MessageManager
117 .getString("label.copy_consensus_sequence");
119 private static final String ADJUST_ANNOTATION_LABELS_WIDTH_PREF = "ADJUST_ANNOTATION_LABELS_WIDTH";
121 private final boolean debugRedraw = false;
123 private AlignmentPanel ap;
127 private MouseEvent dragEvent;
131 private int selectedRow;
133 private int scrollOffset = 0;
135 private boolean hasHiddenRows;
137 private boolean resizePanel = false;
139 private int annotationIdWidth = -1;
142 * Creates a new AnnotationLabels object
146 public AnnotationLabels(AlignmentPanel ap)
150 ToolTipManager.sharedInstance().registerComponent(this);
152 addMouseListener(this);
153 addMouseMotionListener(this);
154 addMouseWheelListener(ap.getAnnotationPanel());
157 public AnnotationLabels(AlignViewport av)
168 public void setScrollOffset(int y)
175 * sets selectedRow to -2 if no annotation preset, -1 if no visible row is at
179 * coordinate position to search for a row
181 void getSelectedRow(int y)
184 AlignmentAnnotation[] aa = ap.av.getAlignment()
185 .getAlignmentAnnotation();
189 for (int i = 0; i < aa.length; i++)
197 height += aa[i].height;
216 public void actionPerformed(ActionEvent evt)
218 AlignmentAnnotation[] aa = ap.av.getAlignment()
219 .getAlignmentAnnotation();
221 String action = evt.getActionCommand();
222 if (ADDNEW.equals(action))
225 * non-returning dialog
227 AlignmentAnnotation newAnnotation = new AlignmentAnnotation(null,
228 null, new Annotation[ap.av.getAlignment().getWidth()]);
229 editLabelDescription(newAnnotation, true);
231 else if (EDITNAME.equals(action))
234 * non-returning dialog
236 editLabelDescription(aa[selectedRow], false);
238 else if (HIDE.equals(action))
240 aa[selectedRow].visible = false;
242 else if (DELETE.equals(action))
244 ap.av.getAlignment().deleteAnnotation(aa[selectedRow]);
245 ap.av.getCalcManager().removeWorkerForAnnotation(aa[selectedRow]);
247 else if (SHOWALL.equals(action))
249 for (int i = 0; i < aa.length; i++)
251 if (!aa[i].visible && aa[i].annotations != null)
253 aa[i].visible = true;
257 else if (OUTPUT_TEXT.equals(action))
259 new AnnotationExporter(ap).exportAnnotation(aa[selectedRow]);
261 else if (COPYCONS_SEQ.equals(action))
263 SequenceI cons = null;
264 if (aa[selectedRow].groupRef != null)
266 cons = aa[selectedRow].groupRef.getConsensusSeq();
270 cons = av.getConsensusSeq();
274 copy_annotseqtoclipboard(cons);
277 else if (TOGGLE_LABELSCALE.equals(action))
279 aa[selectedRow].scaleColLabel = !aa[selectedRow].scaleColLabel;
286 * Shows a dialog where the annotation name and description may be edited. If
287 * parameter addNew is true, then on confirmation, a new AlignmentAnnotation
288 * is added, else an existing annotation is updated.
293 void editLabelDescription(AlignmentAnnotation annotation, boolean addNew)
295 String name = MessageManager.getString("label.annotation_name");
296 String description = MessageManager
297 .getString("label.annotation_description");
298 String title = MessageManager
299 .getString("label.edit_annotation_name_description");
300 EditNameDialog dialog = new EditNameDialog(annotation.label,
301 annotation.description, name, description);
303 dialog.showDialog(ap.alignFrame, title, () -> {
304 annotation.label = dialog.getName();
305 String text = dialog.getDescription();
306 if (text != null && text.length() == 0)
310 annotation.description = text;
313 ap.av.getAlignment().addAnnotation(annotation);
314 ap.av.getAlignment().setAnnotationIndex(annotation, 0);
321 public void mousePressed(MouseEvent evt)
323 getSelectedRow(evt.getY() - getScrollOffset());
325 if (evt.isPopupTrigger())
332 * Build and show the Pop-up menu at the right-click mouse position
336 void showPopupMenu(MouseEvent evt)
339 final AlignmentAnnotation[] aa = ap.av.getAlignment()
340 .getAlignmentAnnotation();
342 JPopupMenu pop = new JPopupMenu(
343 MessageManager.getString("label.annotations"));
344 JMenuItem item = new JMenuItem(ADDNEW);
345 item.addActionListener(this);
350 { // let the user make everything visible again
351 item = new JMenuItem(SHOWALL);
352 item.addActionListener(this);
355 pop.show(this, evt.getX(), evt.getY());
358 item = new JMenuItem(EDITNAME);
359 item.addActionListener(this);
361 item = new JMenuItem(HIDE);
362 item.addActionListener(this);
364 // JAL-1264 hide all sequence-specific annotations of this type
365 if (selectedRow < aa.length)
367 if (aa[selectedRow].sequenceRef != null)
369 final String label = aa[selectedRow].label;
370 JMenuItem hideType = new JMenuItem();
371 String text = MessageManager.getString("label.hide_all") + " "
373 hideType.setText(text);
374 hideType.addActionListener(new ActionListener()
377 public void actionPerformed(ActionEvent e)
379 AlignmentUtils.showOrHideSequenceAnnotations(
380 ap.av.getAlignment(), Collections.singleton(label),
388 item = new JMenuItem(DELETE);
389 item.addActionListener(this);
393 item = new JMenuItem(SHOWALL);
394 item.addActionListener(this);
397 item = new JMenuItem(OUTPUT_TEXT);
398 item.addActionListener(this);
400 // TODO: annotation object should be typed for autocalculated/derived
402 if (selectedRow < aa.length)
404 final String label = aa[selectedRow].label;
405 if (!aa[selectedRow].autoCalculated)
407 if (aa[selectedRow].graph == AlignmentAnnotation.NO_GRAPH)
409 // display formatting settings for this row.
411 // av and sequencegroup need to implement same interface for
412 item = new JCheckBoxMenuItem(TOGGLE_LABELSCALE,
413 aa[selectedRow].scaleColLabel);
414 item.addActionListener(this);
418 else if (label.indexOf("Consensus") > -1)
420 addConsensusMenuOptions(ap, aa[selectedRow], pop);
422 final JMenuItem consclipbrd = new JMenuItem(COPYCONS_SEQ);
423 consclipbrd.addActionListener(this);
424 pop.add(consclipbrd);
427 addColourOrFilterByOptions(ap, aa[selectedRow], pop);
429 if (aa[selectedRow].graph == AlignmentAnnotation.CONTACT_MAP)
431 addContactMatrixOptions(ap, aa[selectedRow], pop);
432 // Set/adjust threshold for grouping ?
433 // colour alignment by this [type]
434 // select/hide columns by this row
439 pop.show(this, evt.getX(), evt.getY());
442 static void addColourOrFilterByOptions(final AlignmentPanel ap,
443 final AlignmentAnnotation alignmentAnnotation,
444 final JPopupMenu pop)
447 item = new JMenuItem(
448 MessageManager.getString("label.colour_by_annotation"));
449 item.addActionListener(new ActionListener()
453 public void actionPerformed(ActionEvent e)
455 AnnotationColourChooser.displayFor(ap.av, ap, alignmentAnnotation,
460 if (alignmentAnnotation.sequenceRef != null)
462 item = new JMenuItem(
463 MessageManager.getString("label.colour_by_annotation") + " ("
464 + MessageManager.getString("label.per_seq") + ")");
465 item.addActionListener(new ActionListener()
468 public void actionPerformed(ActionEvent e)
470 AnnotationColourChooser.displayFor(ap.av, ap, alignmentAnnotation,
476 item = new JMenuItem(
477 MessageManager.getString("action.select_by_annotation"));
478 item.addActionListener(new ActionListener()
482 public void actionPerformed(ActionEvent e)
484 AnnotationColumnChooser.displayFor(ap.av, ap, alignmentAnnotation);
490 static void addContactMatrixOptions(final AlignmentPanel ap,
491 final AlignmentAnnotation alignmentAnnotation,
492 final JPopupMenu pop)
495 final ContactMatrixI cm = ap.av.getContactMatrix(alignmentAnnotation);
503 JCheckBoxMenuItem chitem = new JCheckBoxMenuItem(
504 MessageManager.getString("action.show_groups_on_matrix"));
505 chitem.setToolTipText(MessageManager
506 .getString("action.show_groups_on_matrix_tooltip"));
507 boolean showGroups = alignmentAnnotation
508 .isShowGroupsForContactMatrix();
509 final AlignmentAnnotation sel_row = alignmentAnnotation;
510 chitem.setState(showGroups);
511 chitem.addActionListener(new ActionListener()
515 public void actionPerformed(ActionEvent e)
517 sel_row.setShowGroupsForContactMatrix(chitem.getState());
518 ap.getAnnotationPanel()
519 .paint(ap.getAnnotationPanel().getGraphics());
526 item = new JMenuItem(
527 MessageManager.getString("action.show_tree_for_matrix"));
528 item.setToolTipText(MessageManager
529 .getString("action.show_tree_for_matrix_tooltip"));
530 item.addActionListener(new ActionListener()
534 public void actionPerformed(ActionEvent e)
537 ap.alignFrame.showContactMapTree(alignmentAnnotation, cm);
545 item = new JMenuItem(
546 MessageManager.getString("action.cluster_matrix"));
548 MessageManager.getString("action.cluster_matrix_tooltip"));
549 item.addActionListener(new ActionListener()
552 public void actionPerformed(ActionEvent e)
554 new Thread(new Runnable()
560 ap.alignFrame.setProgressBar(
561 MessageManager.formatMessage(
562 "action.clustering_matrix_for",
563 cm.getAnnotDescr(), 5f),
564 progBar = System.currentTimeMillis());
565 cm.setGroupSet(GroupSet.makeGroups(cm, 5f, true));
566 ap.alignFrame.showContactMapTree(alignmentAnnotation, cm);
567 ap.alignFrame.setProgressBar(null, progBar);
578 * A helper method that adds menu options for calculation and visualisation of
579 * group and/or alignment consensus annotation to a popup menu. This is
580 * designed to be reusable for either unwrapped mode (popup menu is shown on
581 * component AnnotationLabels), or wrapped mode (popup menu is shown on
582 * IdPanel when the mouse is over an annotation label).
588 static void addConsensusMenuOptions(AlignmentPanel ap,
589 AlignmentAnnotation ann, JPopupMenu pop)
593 final JCheckBoxMenuItem cbmi = new JCheckBoxMenuItem(
594 MessageManager.getString("label.ignore_gaps_consensus"),
595 (ann.groupRef != null) ? ann.groupRef.getIgnoreGapsConsensus()
596 : ap.av.isIgnoreGapsConsensus());
597 final AlignmentAnnotation aaa = ann;
598 cbmi.addActionListener(new ActionListener()
601 public void actionPerformed(ActionEvent e)
603 if (aaa.groupRef != null)
605 aaa.groupRef.setIgnoreGapsConsensus(cbmi.getState());
606 ap.getAnnotationPanel()
607 .paint(ap.getAnnotationPanel().getGraphics());
611 ap.av.setIgnoreGapsConsensus(cbmi.getState(), ap);
613 ap.alignmentChanged();
618 if (aaa.groupRef != null)
621 * group consensus options
623 final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
624 MessageManager.getString("label.show_group_histogram"),
625 ann.groupRef.isShowConsensusHistogram());
626 chist.addActionListener(new ActionListener()
629 public void actionPerformed(ActionEvent e)
631 aaa.groupRef.setShowConsensusHistogram(chist.getState());
636 final JCheckBoxMenuItem cprofl = new JCheckBoxMenuItem(
637 MessageManager.getString("label.show_group_logo"),
638 ann.groupRef.isShowSequenceLogo());
639 cprofl.addActionListener(new ActionListener()
642 public void actionPerformed(ActionEvent e)
644 aaa.groupRef.setshowSequenceLogo(cprofl.getState());
649 final JCheckBoxMenuItem cproflnorm = new JCheckBoxMenuItem(
650 MessageManager.getString("label.normalise_group_logo"),
651 ann.groupRef.isNormaliseSequenceLogo());
652 cproflnorm.addActionListener(new ActionListener()
655 public void actionPerformed(ActionEvent e)
657 aaa.groupRef.setNormaliseSequenceLogo(cproflnorm.getState());
658 // automatically enable logo display if we're clicked
659 aaa.groupRef.setshowSequenceLogo(true);
668 * alignment consensus options
670 final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
671 MessageManager.getString("label.show_histogram"),
672 ap.av.isShowConsensusHistogram());
673 chist.addActionListener(new ActionListener()
676 public void actionPerformed(ActionEvent e)
678 ap.av.setShowConsensusHistogram(chist.getState());
679 ap.alignFrame.setMenusForViewport();
684 final JCheckBoxMenuItem cprof = new JCheckBoxMenuItem(
685 MessageManager.getString("label.show_logo"),
686 ap.av.isShowSequenceLogo());
687 cprof.addActionListener(new ActionListener()
690 public void actionPerformed(ActionEvent e)
692 ap.av.setShowSequenceLogo(cprof.getState());
693 ap.alignFrame.setMenusForViewport();
698 final JCheckBoxMenuItem cprofnorm = new JCheckBoxMenuItem(
699 MessageManager.getString("label.normalise_logo"),
700 ap.av.isNormaliseSequenceLogo());
701 cprofnorm.addActionListener(new ActionListener()
704 public void actionPerformed(ActionEvent e)
706 ap.av.setShowSequenceLogo(true);
707 ap.av.setNormaliseSequenceLogo(cprofnorm.getState());
708 ap.alignFrame.setMenusForViewport();
717 * Reorders annotation rows after a drag of a label
722 public void mouseReleased(MouseEvent evt)
724 if (evt.isPopupTrigger())
730 int start = selectedRow;
731 getSelectedRow(evt.getY() - getScrollOffset());
732 int end = selectedRow;
735 * if dragging to resize instead, start == end
739 // Swap these annotations
740 AlignmentAnnotation startAA = ap.av.getAlignment()
741 .getAlignmentAnnotation()[start];
744 end = ap.av.getAlignment().getAlignmentAnnotation().length - 1;
746 AlignmentAnnotation endAA = ap.av.getAlignment()
747 .getAlignmentAnnotation()[end];
749 ap.av.getAlignment().getAlignmentAnnotation()[end] = startAA;
750 ap.av.getAlignment().getAlignmentAnnotation()[start] = endAA;
756 ap.getAnnotationPanel().repaint();
760 * Removes the height adjuster image on leaving the panel, unless currently
764 public void mouseExited(MouseEvent evt)
766 if (resizePanel && dragEvent == null)
774 * A mouse drag may be either an adjustment of the panel height (if flag
775 * resizePanel is set on), or a reordering of the annotation rows. The former
776 * is dealt with by this method, the latter in mouseReleased.
781 public void mouseDragged(MouseEvent evt)
787 Dimension d = ap.annotationScroller.getPreferredSize();
788 int dif = evt.getY() - oldY;
790 dif /= ap.av.getCharHeight();
791 dif *= ap.av.getCharHeight();
793 if ((d.height - dif) > 20)
795 ap.annotationScroller
796 .setPreferredSize(new Dimension(d.width, d.height - dif));
797 d = ap.annotationSpaceFillerHolder.getPreferredSize();
798 ap.annotationSpaceFillerHolder
799 .setPreferredSize(new Dimension(d.width, d.height - dif));
800 ap.paintAlignment(true, false);
812 * Updates the tooltip as the mouse moves over the labels
817 public void mouseMoved(MouseEvent evt)
819 showOrHideAdjuster(evt);
821 getSelectedRow(evt.getY() - getScrollOffset());
823 if (selectedRow > -1 && ap.av.getAlignment()
824 .getAlignmentAnnotation().length > selectedRow)
826 AlignmentAnnotation[] anns = ap.av.getAlignment()
827 .getAlignmentAnnotation();
828 AlignmentAnnotation aa = anns[selectedRow];
830 String desc = getTooltip(aa);
831 this.setToolTipText(desc);
832 String msg = getStatusMessage(aa, anns);
833 ap.alignFrame.setStatus(msg);
838 * Constructs suitable text to show in the status bar when over an annotation
839 * label, containing the associated sequence name (if any), and the annotation
840 * labels (or all labels for a graph group annotation)
846 static String getStatusMessage(AlignmentAnnotation aa,
847 AlignmentAnnotation[] anns)
854 StringBuilder msg = new StringBuilder(32);
855 if (aa.sequenceRef != null)
857 msg.append(aa.sequenceRef.getName()).append(" : ");
860 if (aa.graphGroup == -1)
862 msg.append(aa.label);
864 else if (anns != null)
866 boolean first = true;
867 for (int i = anns.length - 1; i >= 0; i--)
869 if (anns[i].graphGroup == aa.graphGroup)
875 msg.append(anns[i].label);
881 return msg.toString();
885 * Answers a tooltip, formatted as html, containing the annotation description
886 * (prefixed by associated sequence id if applicable), and the annotation
887 * (non-positional) score if it has one. Answers null if neither description
888 * nor score is found.
893 static String getTooltip(AlignmentAnnotation aa)
899 StringBuilder tooltip = new StringBuilder();
900 if (aa.description != null && !aa.description.equals("New description"))
902 // TODO: we could refactor and merge this code with the code in
903 // jalview.gui.SeqPanel.mouseMoved(..) that formats sequence feature
905 String desc = aa.getDescription(true).trim();
906 if (!desc.toLowerCase(Locale.ROOT).startsWith(HTML_START_TAG))
908 tooltip.append(HTML_START_TAG);
909 desc = desc.replace("<", "<");
911 else if (desc.toLowerCase(Locale.ROOT).endsWith(HTML_END_TAG))
913 desc = desc.substring(0, desc.length() - HTML_END_TAG.length());
915 tooltip.append(desc);
919 // begin the tooltip's html fragment
920 tooltip.append(HTML_START_TAG);
924 if (tooltip.length() > HTML_START_TAG.length())
926 tooltip.append("<br/>");
928 // TODO: limit precision of score to avoid noise from imprecise
930 // (64.7 becomes 64.7+/some tiny value).
931 tooltip.append(" Score: ").append(String.valueOf(aa.score));
934 if (tooltip.length() > HTML_START_TAG.length())
936 return tooltip.append(HTML_END_TAG).toString();
940 * nothing in the tooltip (except "<html>")
946 * Shows the height adjuster image if the mouse moves into the top left
947 * region, or hides it if the mouse leaves the regio
951 protected void showOrHideAdjuster(MouseEvent evt)
953 boolean was = resizePanel;
954 resizePanel = evt.getY() < HEIGHT_ADJUSTER_HEIGHT
955 && evt.getX() < HEIGHT_ADJUSTER_WIDTH;
957 if (resizePanel != was)
960 .getPredefinedCursor(resizePanel ? Cursor.S_RESIZE_CURSOR
961 : Cursor.DEFAULT_CURSOR));
967 public void mouseClicked(MouseEvent evt)
969 final AlignmentAnnotation[] aa = ap.av.getAlignment()
970 .getAlignmentAnnotation();
971 if (!evt.isPopupTrigger() && SwingUtilities.isLeftMouseButton(evt))
973 if (selectedRow > -1 && selectedRow < aa.length)
975 if (aa[selectedRow].groupRef != null)
977 if (evt.getClickCount() >= 2)
979 // todo: make the ap scroll to the selection - not necessary, first
980 // click highlights/scrolls, second selects
981 ap.getSeqPanel().ap.getIdPanel().highlightSearchResults(null);
983 SequenceGroup sg = ap.av.getSelectionGroup();
984 if (sg == null || sg == aa[selectedRow].groupRef
985 || !(Platform.isControlDown(evt) || evt.isShiftDown()))
987 if (Platform.isControlDown(evt) || evt.isShiftDown())
989 // clone a new selection group from the associated group
990 ap.av.setSelectionGroup(
991 new SequenceGroup(aa[selectedRow].groupRef));
995 // set selection to the associated group so it can be edited
996 ap.av.setSelectionGroup(aa[selectedRow].groupRef);
1001 // modify current selection with associated group
1002 int remainToAdd = aa[selectedRow].groupRef.getSize();
1003 for (SequenceI sgs : aa[selectedRow].groupRef.getSequences())
1005 if (jalview.util.Platform.isControlDown(evt))
1007 sg.addOrRemove(sgs, --remainToAdd == 0);
1011 // notionally, we should also add intermediate sequences from
1012 // last added sequence ?
1013 sg.addSequence(sgs, --remainToAdd == 0);
1018 ap.paintAlignment(false, false);
1019 PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
1020 ap.av.sendSelection();
1024 ap.getSeqPanel().ap.getIdPanel().highlightSearchResults(
1025 aa[selectedRow].groupRef.getSequences(null));
1029 else if (aa[selectedRow].sequenceRef != null)
1031 if (evt.getClickCount() == 1)
1033 ap.getSeqPanel().ap.getIdPanel()
1034 .highlightSearchResults(Arrays.asList(new SequenceI[]
1035 { aa[selectedRow].sequenceRef }));
1037 else if (evt.getClickCount() >= 2)
1039 ap.getSeqPanel().ap.getIdPanel().highlightSearchResults(null);
1040 SequenceGroup sg = ap.av.getSelectionGroup();
1043 // we make a copy rather than edit the current selection if no
1044 // modifiers pressed
1045 // see Enhancement JAL-1557
1046 if (!(Platform.isControlDown(evt) || evt.isShiftDown()))
1048 sg = new SequenceGroup(sg);
1050 sg.addSequence(aa[selectedRow].sequenceRef, false);
1054 if (Platform.isControlDown(evt))
1056 sg.addOrRemove(aa[selectedRow].sequenceRef, true);
1060 // notionally, we should also add intermediate sequences from
1061 // last added sequence ?
1062 sg.addSequence(aa[selectedRow].sequenceRef, true);
1068 sg = new SequenceGroup();
1070 sg.setEndRes(ap.av.getAlignment().getWidth() - 1);
1071 sg.addSequence(aa[selectedRow].sequenceRef, false);
1073 ap.av.setSelectionGroup(sg);
1074 ap.paintAlignment(false, false);
1075 PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
1076 ap.av.sendSelection();
1086 * do a single sequence copy to jalview and the system clipboard
1089 * sequence to be copied to clipboard
1091 protected void copy_annotseqtoclipboard(SequenceI sq)
1093 SequenceI[] seqs = new SequenceI[] { sq };
1094 String[] omitHidden = null;
1095 SequenceI[] dseqs = new SequenceI[] { sq.getDatasetSequence() };
1096 if (dseqs[0] == null)
1098 dseqs[0] = new Sequence(sq);
1099 dseqs[0].setSequence(AlignSeq.extractGaps(Comparison.GapChars,
1100 sq.getSequenceAsString()));
1102 sq.setDatasetSequence(dseqs[0]);
1104 Alignment ds = new Alignment(dseqs);
1105 if (av.hasHiddenColumns())
1107 Iterator<int[]> it = av.getAlignment().getHiddenColumns()
1108 .getVisContigsIterator(0, sq.getLength(), false);
1109 omitHidden = new String[] { sq.getSequenceStringFromIterator(it) };
1112 int[] alignmentStartEnd = new int[] { 0, ds.getWidth() - 1 };
1113 if (av.hasHiddenColumns())
1115 alignmentStartEnd = av.getAlignment().getHiddenColumns()
1116 .getVisibleStartAndEndIndex(av.getAlignment().getWidth());
1119 String output = new FormatAdapter().formatSequences(FileFormat.Fasta,
1120 seqs, omitHidden, alignmentStartEnd);
1122 Toolkit.getDefaultToolkit().getSystemClipboard()
1123 .setContents(new StringSelection(output), Desktop.instance);
1125 HiddenColumns hiddenColumns = null;
1127 if (av.hasHiddenColumns())
1129 hiddenColumns = new HiddenColumns(
1130 av.getAlignment().getHiddenColumns());
1133 Desktop.jalviewClipboard = new Object[] { seqs, ds, // what is the dataset
1135 // sequence ? need to
1137 // sequence as special.
1148 public void paintComponent(Graphics g)
1151 int width = getWidth();
1154 width = ap.calculateIdWidth().width;
1157 Graphics2D g2 = (Graphics2D) g;
1160 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
1161 RenderingHints.VALUE_ANTIALIAS_ON);
1164 drawComponent(g2, true, width);
1169 * Draw the full set of annotation Labels for the alignment at the given
1173 * Graphics2D instance (needed for font scaling)
1175 * Width for scaling labels
1178 public void drawComponent(Graphics g, int width)
1180 drawComponent(g, false, width);
1184 * Draw the full set of annotation Labels for the alignment at the given
1188 * Graphics2D instance (needed for font scaling)
1190 * - true indicates that only current visible area needs to be
1193 * Width for scaling labels
1195 public void drawComponent(Graphics g, boolean clip, int givenWidth)
1197 int width = givenWidth;
1198 IdwidthAdjuster iwa = null;
1201 iwa = ap.idwidthAdjuster;
1202 if ((Cache.getDefault(ADJUST_ANNOTATION_LABELS_WIDTH_PREF, true)
1203 || Jalview.isHeadlessMode()))
1205 Graphics2D g2d = (Graphics2D) g;
1206 Graphics dummy = g2d.create();
1207 int newAnnotationIdWidth = drawLabels(dummy, clip, width, false,
1210 Dimension d = ap.calculateDefaultAlignmentIdWidth();
1211 int alignmentIdWidth = d.width;
1212 if (iwa != null && !iwa.manuallyAdjusted())
1214 // If no manual adjustment to ID column with has been made then adjust
1215 // width match widest of alignment or annotation id widths
1216 width = Math.max(alignmentIdWidth, newAnnotationIdWidth);
1218 else if (newAnnotationIdWidth != annotationIdWidth
1219 && newAnnotationIdWidth > givenWidth
1220 && newAnnotationIdWidth > alignmentIdWidth)
1222 // otherwise if the annotation id width has become larger than the
1223 // current id width, increase
1224 width = newAnnotationIdWidth;
1225 annotationIdWidth = newAnnotationIdWidth;
1227 // set the width if it's changed
1228 if (width != ap.av.getIdWidth())
1230 iwa.setWidth(width);
1236 Graphics2D g2d = (Graphics2D) g;
1237 Graphics dummy = g2d.create();
1238 int newAnnotationIdWidth = drawLabels(dummy, clip, width, false,
1240 width = Math.max(newAnnotationIdWidth, givenWidth);
1242 drawLabels(g, clip, width, true, null);
1246 * Render the full set of annotation Labels for the alignment at the given
1247 * cursor. If actuallyDraw is false or g is null then no actual drawing will
1248 * occur, but the widest label width will be returned. If g is null then
1249 * fmetrics must be supplied.
1251 * Returns the width of the annotation labels.
1254 * Graphics2D instance (needed for font scaling)
1256 * - true indicates that only current visible area needs to be
1259 * Width for scaling labels
1261 * FontMetrics if Graphics object g is null
1263 public int drawLabels(Graphics g, boolean clip, int width,
1264 boolean actuallyDraw, FontMetrics fmetrics)
1266 int actualWidth = 0;
1269 if (av.getFont().getSize() < 10)
1275 g.setFont(av.getFont());
1279 FontMetrics fm = fmetrics == null ? g.getFontMetrics(g.getFont())
1283 g.setColor(Color.white);
1284 g.fillRect(0, 0, getWidth(), getHeight());
1289 g.translate(0, getScrollOffset());
1290 g.setColor(Color.black);
1292 SequenceI lastSeqRef = null;
1293 String lastLabel = null;
1294 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
1295 int fontHeight = g != null ? g.getFont().getSize()
1296 : fm.getFont().getSize();
1299 int graphExtras = 0;
1301 Font baseFont = g != null ? g.getFont() : fm.getFont();
1302 FontMetrics baseMetrics = fm;
1303 int ofontH = fontHeight;
1306 int[] visr = (ap != null && ap.getAnnotationPanel() != null)
1307 ? ap.getAnnotationPanel().getVisibleVRange()
1309 if (clip && visr != null)
1312 visHeight = visr[1];
1314 boolean visible = true, before = false, after = false;
1317 hasHiddenRows = false;
1320 for (int i = 0; i < aa.length; i++)
1325 hasHiddenRows = true;
1329 // look ahead to next annotation
1330 for (nexAA = i + 1; nexAA < aa.length
1331 && !aa[nexAA].visible; nexAA++)
1342 System.out.println("before vis: " + i);
1346 // don't draw what isn't visible
1349 if (olY > visHeight)
1357 "Scroll offset: " + sOffset + " after vis: " + i);
1361 // don't draw what isn't visible
1365 if (actuallyDraw && g != null)
1367 g.setColor(Color.black);
1369 offset = -aa[i].height / 2;
1373 offset += fm.getHeight() / 2;
1374 offset -= fm.getDescent();
1378 offset += fm.getDescent();
1380 String label = aa[i].label;
1381 boolean vertBar = false;
1382 if ((lastLabel != null && lastLabel.equals(label)))
1384 label = aa[i].description;
1388 if (nexAA < aa.length && label.equals(aa[nexAA].label)) // &&
1389 // aa[nexY].sequenceRef==aa[i].sequenceRef)
1392 // next label is the same as this label
1393 label = aa[i].description;
1400 if (aa[i].sequenceRef != null)
1402 if (aa[i].sequenceRef != lastSeqRef)
1404 label = aa[i].sequenceRef.getName() + " " + label;
1405 // TODO record relationship between sequence and this annotation and
1414 int labelWidth = fm.stringWidth(label) + 3;
1415 x = width - labelWidth;
1417 if (aa[i].graphGroup > -1)
1420 // TODO: JAL-1291 revise rendering model so the graphGroup map is
1421 // computed efficiently for all visible labels
1422 for (int gg = 0; gg < aa.length; gg++)
1424 if (aa[gg].graphGroup == aa[i].graphGroup)
1429 if (groupSize * (fontHeight + 8) < aa[i].height)
1431 graphExtras = (aa[i].height - (groupSize * (fontHeight + 8)))
1436 // scale font to fit
1437 float h = aa[i].height / (float) groupSize, s;
1444 fontHeight = -8 + (int) h;
1445 s = ((float) fontHeight) / (float) ofontH;
1447 .deriveFont(AffineTransform.getScaleInstance(s, s));
1448 Canvas c = new Canvas();
1449 fm = c.getFontMetrics(f);
1450 if (actuallyDraw && g != null)
1453 // fm = g.getFontMetrics();
1454 graphExtras = (aa[i].height
1455 - (groupSize * (fontHeight + 8))) / 2;
1461 for (int gg = 0; gg < aa.length; gg++)
1463 if (aa[gg].graphGroup == aa[i].graphGroup)
1465 labelWidth = fm.stringWidth(aa[gg].label) + 3;
1466 x = width - labelWidth;
1467 if (actuallyDraw && g != null)
1469 g.drawString(aa[gg].label, x, y - graphExtras);
1471 if (aa[gg]._linecolour != null)
1474 g.setColor(aa[gg]._linecolour);
1475 g.drawLine(x, y - graphExtras + 3,
1476 x + fm.stringWidth(aa[gg].label),
1477 y - graphExtras + 3);
1480 g.setColor(Color.black);
1482 graphExtras += fontHeight + 8;
1486 if (actuallyDraw && g != null)
1488 g.setFont(baseFont);
1491 fontHeight = ofontH;
1495 if (actuallyDraw && g != null)
1499 g.drawLine(width - 3, y + offset - fontHeight, width - 3,
1500 (int) (y - 1.5 * aa[i].height - offset - fontHeight));
1501 // g.drawLine(20, y + offset, x - 20, y + offset);
1504 g.drawString(label, x, y + offset);
1507 lastSeqRef = aa[i].sequenceRef;
1509 if (labelWidth > actualWidth)
1511 actualWidth = labelWidth;
1516 if (!resizePanel && dragEvent != null && aa != null)
1518 if (actuallyDraw && g != null)
1520 g.setColor(Color.lightGray);
1522 (aa[selectedRow].sequenceRef == null ? ""
1523 : aa[selectedRow].sequenceRef.getName())
1524 + aa[selectedRow].label,
1525 dragEvent.getX(), dragEvent.getY() - getScrollOffset());
1529 if (!av.getWrapAlignment() && ((aa == null) || (aa.length < 1)))
1531 if (actuallyDraw && g != null)
1533 g.drawString(MessageManager.getString("label.right_click"), 2, 8);
1534 g.drawString(MessageManager.getString("label.to_add_annotation"), 2,
1542 public int getScrollOffset()
1544 return scrollOffset;
1548 public void mouseEntered(MouseEvent e)