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 // so any annotation colour changes are propagated - though they
519 // probably won't be unless the annotation row colours are removed
521 ap.alignmentChanged();
528 item = new JMenuItem(
529 MessageManager.getString("action.show_tree_for_matrix"));
530 item.setToolTipText(MessageManager
531 .getString("action.show_tree_for_matrix_tooltip"));
532 item.addActionListener(new ActionListener()
536 public void actionPerformed(ActionEvent e)
539 ap.alignFrame.showContactMapTree(alignmentAnnotation, cm);
547 item = new JMenuItem(
548 MessageManager.getString("action.cluster_matrix"));
550 MessageManager.getString("action.cluster_matrix_tooltip"));
551 item.addActionListener(new ActionListener()
554 public void actionPerformed(ActionEvent e)
556 new Thread(new Runnable()
562 ap.alignFrame.setProgressBar(
563 MessageManager.formatMessage(
564 "action.clustering_matrix_for",
565 cm.getAnnotDescr(), 5f),
566 progBar = System.currentTimeMillis());
567 cm.setGroupSet(GroupSet.makeGroups(cm, true));
568 cm.randomlyReColourGroups();
569 cm.transferGroupColorsTo(alignmentAnnotation);
570 ap.alignmentChanged();
571 ap.alignFrame.showContactMapTree(alignmentAnnotation, cm);
572 ap.alignFrame.setProgressBar(null, progBar);
583 * A helper method that adds menu options for calculation and visualisation of
584 * group and/or alignment consensus annotation to a popup menu. This is
585 * designed to be reusable for either unwrapped mode (popup menu is shown on
586 * component AnnotationLabels), or wrapped mode (popup menu is shown on
587 * IdPanel when the mouse is over an annotation label).
593 static void addConsensusMenuOptions(AlignmentPanel ap,
594 AlignmentAnnotation ann, JPopupMenu pop)
598 final JCheckBoxMenuItem cbmi = new JCheckBoxMenuItem(
599 MessageManager.getString("label.ignore_gaps_consensus"),
600 (ann.groupRef != null) ? ann.groupRef.getIgnoreGapsConsensus()
601 : ap.av.isIgnoreGapsConsensus());
602 final AlignmentAnnotation aaa = ann;
603 cbmi.addActionListener(new ActionListener()
606 public void actionPerformed(ActionEvent e)
608 if (aaa.groupRef != null)
610 aaa.groupRef.setIgnoreGapsConsensus(cbmi.getState());
611 ap.getAnnotationPanel()
612 .paint(ap.getAnnotationPanel().getGraphics());
616 ap.av.setIgnoreGapsConsensus(cbmi.getState(), ap);
618 ap.alignmentChanged();
623 if (aaa.groupRef != null)
626 * group consensus options
628 final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
629 MessageManager.getString("label.show_group_histogram"),
630 ann.groupRef.isShowConsensusHistogram());
631 chist.addActionListener(new ActionListener()
634 public void actionPerformed(ActionEvent e)
636 aaa.groupRef.setShowConsensusHistogram(chist.getState());
641 final JCheckBoxMenuItem cprofl = new JCheckBoxMenuItem(
642 MessageManager.getString("label.show_group_logo"),
643 ann.groupRef.isShowSequenceLogo());
644 cprofl.addActionListener(new ActionListener()
647 public void actionPerformed(ActionEvent e)
649 aaa.groupRef.setshowSequenceLogo(cprofl.getState());
654 final JCheckBoxMenuItem cproflnorm = new JCheckBoxMenuItem(
655 MessageManager.getString("label.normalise_group_logo"),
656 ann.groupRef.isNormaliseSequenceLogo());
657 cproflnorm.addActionListener(new ActionListener()
660 public void actionPerformed(ActionEvent e)
662 aaa.groupRef.setNormaliseSequenceLogo(cproflnorm.getState());
663 // automatically enable logo display if we're clicked
664 aaa.groupRef.setshowSequenceLogo(true);
673 * alignment consensus options
675 final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
676 MessageManager.getString("label.show_histogram"),
677 ap.av.isShowConsensusHistogram());
678 chist.addActionListener(new ActionListener()
681 public void actionPerformed(ActionEvent e)
683 ap.av.setShowConsensusHistogram(chist.getState());
684 ap.alignFrame.setMenusForViewport();
689 final JCheckBoxMenuItem cprof = new JCheckBoxMenuItem(
690 MessageManager.getString("label.show_logo"),
691 ap.av.isShowSequenceLogo());
692 cprof.addActionListener(new ActionListener()
695 public void actionPerformed(ActionEvent e)
697 ap.av.setShowSequenceLogo(cprof.getState());
698 ap.alignFrame.setMenusForViewport();
703 final JCheckBoxMenuItem cprofnorm = new JCheckBoxMenuItem(
704 MessageManager.getString("label.normalise_logo"),
705 ap.av.isNormaliseSequenceLogo());
706 cprofnorm.addActionListener(new ActionListener()
709 public void actionPerformed(ActionEvent e)
711 ap.av.setShowSequenceLogo(true);
712 ap.av.setNormaliseSequenceLogo(cprofnorm.getState());
713 ap.alignFrame.setMenusForViewport();
722 * Reorders annotation rows after a drag of a label
727 public void mouseReleased(MouseEvent evt)
729 if (evt.isPopupTrigger())
735 int start = selectedRow;
736 getSelectedRow(evt.getY() - getScrollOffset());
737 int end = selectedRow;
740 * if dragging to resize instead, start == end
744 // Swap these annotations
745 AlignmentAnnotation startAA = ap.av.getAlignment()
746 .getAlignmentAnnotation()[start];
749 end = ap.av.getAlignment().getAlignmentAnnotation().length - 1;
751 AlignmentAnnotation endAA = ap.av.getAlignment()
752 .getAlignmentAnnotation()[end];
754 ap.av.getAlignment().getAlignmentAnnotation()[end] = startAA;
755 ap.av.getAlignment().getAlignmentAnnotation()[start] = endAA;
761 ap.getAnnotationPanel().repaint();
765 * Removes the height adjuster image on leaving the panel, unless currently
769 public void mouseExited(MouseEvent evt)
771 if (resizePanel && dragEvent == null)
779 * A mouse drag may be either an adjustment of the panel height (if flag
780 * resizePanel is set on), or a reordering of the annotation rows. The former
781 * is dealt with by this method, the latter in mouseReleased.
786 public void mouseDragged(MouseEvent evt)
792 Dimension d = ap.annotationScroller.getPreferredSize();
793 int dif = evt.getY() - oldY;
795 dif /= ap.av.getCharHeight();
796 dif *= ap.av.getCharHeight();
798 if ((d.height - dif) > 20)
800 ap.annotationScroller
801 .setPreferredSize(new Dimension(d.width, d.height - dif));
802 d = ap.annotationSpaceFillerHolder.getPreferredSize();
803 ap.annotationSpaceFillerHolder
804 .setPreferredSize(new Dimension(d.width, d.height - dif));
805 ap.paintAlignment(true, false);
817 * Updates the tooltip as the mouse moves over the labels
822 public void mouseMoved(MouseEvent evt)
824 showOrHideAdjuster(evt);
826 getSelectedRow(evt.getY() - getScrollOffset());
828 if (selectedRow > -1 && ap.av.getAlignment()
829 .getAlignmentAnnotation().length > selectedRow)
831 AlignmentAnnotation[] anns = ap.av.getAlignment()
832 .getAlignmentAnnotation();
833 AlignmentAnnotation aa = anns[selectedRow];
835 String desc = getTooltip(aa);
836 this.setToolTipText(desc);
837 String msg = getStatusMessage(aa, anns);
838 ap.alignFrame.setStatus(msg);
843 * Constructs suitable text to show in the status bar when over an annotation
844 * label, containing the associated sequence name (if any), and the annotation
845 * labels (or all labels for a graph group annotation)
851 static String getStatusMessage(AlignmentAnnotation aa,
852 AlignmentAnnotation[] anns)
859 StringBuilder msg = new StringBuilder(32);
860 if (aa.sequenceRef != null)
862 msg.append(aa.sequenceRef.getName()).append(" : ");
865 if (aa.graphGroup == -1)
867 msg.append(aa.label);
869 else if (anns != null)
871 boolean first = true;
872 for (int i = anns.length - 1; i >= 0; i--)
874 if (anns[i].graphGroup == aa.graphGroup)
880 msg.append(anns[i].label);
886 return msg.toString();
890 * Answers a tooltip, formatted as html, containing the annotation description
891 * (prefixed by associated sequence id if applicable), and the annotation
892 * (non-positional) score if it has one. Answers null if neither description
893 * nor score is found.
898 static String getTooltip(AlignmentAnnotation aa)
904 StringBuilder tooltip = new StringBuilder();
905 if (aa.description != null && !aa.description.equals("New description"))
907 // TODO: we could refactor and merge this code with the code in
908 // jalview.gui.SeqPanel.mouseMoved(..) that formats sequence feature
910 String desc = aa.getDescription(true).trim();
911 if (!desc.toLowerCase(Locale.ROOT).startsWith(HTML_START_TAG))
913 tooltip.append(HTML_START_TAG);
914 desc = desc.replace("<", "<");
916 else if (desc.toLowerCase(Locale.ROOT).endsWith(HTML_END_TAG))
918 desc = desc.substring(0, desc.length() - HTML_END_TAG.length());
920 tooltip.append(desc);
924 // begin the tooltip's html fragment
925 tooltip.append(HTML_START_TAG);
929 if (tooltip.length() > HTML_START_TAG.length())
931 tooltip.append("<br/>");
933 // TODO: limit precision of score to avoid noise from imprecise
935 // (64.7 becomes 64.7+/some tiny value).
936 tooltip.append(" Score: ").append(String.valueOf(aa.score));
939 if (tooltip.length() > HTML_START_TAG.length())
941 return tooltip.append(HTML_END_TAG).toString();
945 * nothing in the tooltip (except "<html>")
951 * Shows the height adjuster image if the mouse moves into the top left
952 * region, or hides it if the mouse leaves the regio
956 protected void showOrHideAdjuster(MouseEvent evt)
958 boolean was = resizePanel;
959 resizePanel = evt.getY() < HEIGHT_ADJUSTER_HEIGHT
960 && evt.getX() < HEIGHT_ADJUSTER_WIDTH;
962 if (resizePanel != was)
965 .getPredefinedCursor(resizePanel ? Cursor.S_RESIZE_CURSOR
966 : Cursor.DEFAULT_CURSOR));
972 public void mouseClicked(MouseEvent evt)
974 final AlignmentAnnotation[] aa = ap.av.getAlignment()
975 .getAlignmentAnnotation();
976 if (!evt.isPopupTrigger() && SwingUtilities.isLeftMouseButton(evt))
978 if (selectedRow > -1 && selectedRow < aa.length)
980 if (aa[selectedRow].groupRef != null)
982 if (evt.getClickCount() >= 2)
984 // todo: make the ap scroll to the selection - not necessary, first
985 // click highlights/scrolls, second selects
986 ap.getSeqPanel().ap.getIdPanel().highlightSearchResults(null);
988 SequenceGroup sg = ap.av.getSelectionGroup();
989 if (sg == null || sg == aa[selectedRow].groupRef
990 || !(Platform.isControlDown(evt) || evt.isShiftDown()))
992 if (Platform.isControlDown(evt) || evt.isShiftDown())
994 // clone a new selection group from the associated group
995 ap.av.setSelectionGroup(
996 new SequenceGroup(aa[selectedRow].groupRef));
1000 // set selection to the associated group so it can be edited
1001 ap.av.setSelectionGroup(aa[selectedRow].groupRef);
1006 // modify current selection with associated group
1007 int remainToAdd = aa[selectedRow].groupRef.getSize();
1008 for (SequenceI sgs : aa[selectedRow].groupRef.getSequences())
1010 if (jalview.util.Platform.isControlDown(evt))
1012 sg.addOrRemove(sgs, --remainToAdd == 0);
1016 // notionally, we should also add intermediate sequences from
1017 // last added sequence ?
1018 sg.addSequence(sgs, --remainToAdd == 0);
1023 ap.paintAlignment(false, false);
1024 PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
1025 ap.av.sendSelection();
1029 ap.getSeqPanel().ap.getIdPanel().highlightSearchResults(
1030 aa[selectedRow].groupRef.getSequences(null));
1034 else if (aa[selectedRow].sequenceRef != null)
1036 if (evt.getClickCount() == 1)
1038 ap.getSeqPanel().ap.getIdPanel()
1039 .highlightSearchResults(Arrays.asList(new SequenceI[]
1040 { aa[selectedRow].sequenceRef }));
1042 else if (evt.getClickCount() >= 2)
1044 ap.getSeqPanel().ap.getIdPanel().highlightSearchResults(null);
1045 SequenceGroup sg = ap.av.getSelectionGroup();
1048 // we make a copy rather than edit the current selection if no
1049 // modifiers pressed
1050 // see Enhancement JAL-1557
1051 if (!(Platform.isControlDown(evt) || evt.isShiftDown()))
1053 sg = new SequenceGroup(sg);
1055 sg.addSequence(aa[selectedRow].sequenceRef, false);
1059 if (Platform.isControlDown(evt))
1061 sg.addOrRemove(aa[selectedRow].sequenceRef, true);
1065 // notionally, we should also add intermediate sequences from
1066 // last added sequence ?
1067 sg.addSequence(aa[selectedRow].sequenceRef, true);
1073 sg = new SequenceGroup();
1075 sg.setEndRes(ap.av.getAlignment().getWidth() - 1);
1076 sg.addSequence(aa[selectedRow].sequenceRef, false);
1078 ap.av.setSelectionGroup(sg);
1079 ap.paintAlignment(false, false);
1080 PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
1081 ap.av.sendSelection();
1091 * do a single sequence copy to jalview and the system clipboard
1094 * sequence to be copied to clipboard
1096 protected void copy_annotseqtoclipboard(SequenceI sq)
1098 SequenceI[] seqs = new SequenceI[] { sq };
1099 String[] omitHidden = null;
1100 SequenceI[] dseqs = new SequenceI[] { sq.getDatasetSequence() };
1101 if (dseqs[0] == null)
1103 dseqs[0] = new Sequence(sq);
1104 dseqs[0].setSequence(AlignSeq.extractGaps(Comparison.GapChars,
1105 sq.getSequenceAsString()));
1107 sq.setDatasetSequence(dseqs[0]);
1109 Alignment ds = new Alignment(dseqs);
1110 if (av.hasHiddenColumns())
1112 Iterator<int[]> it = av.getAlignment().getHiddenColumns()
1113 .getVisContigsIterator(0, sq.getLength(), false);
1114 omitHidden = new String[] { sq.getSequenceStringFromIterator(it) };
1117 int[] alignmentStartEnd = new int[] { 0, ds.getWidth() - 1 };
1118 if (av.hasHiddenColumns())
1120 alignmentStartEnd = av.getAlignment().getHiddenColumns()
1121 .getVisibleStartAndEndIndex(av.getAlignment().getWidth());
1124 String output = new FormatAdapter().formatSequences(FileFormat.Fasta,
1125 seqs, omitHidden, alignmentStartEnd);
1127 Toolkit.getDefaultToolkit().getSystemClipboard()
1128 .setContents(new StringSelection(output), Desktop.instance);
1130 HiddenColumns hiddenColumns = null;
1132 if (av.hasHiddenColumns())
1134 hiddenColumns = new HiddenColumns(
1135 av.getAlignment().getHiddenColumns());
1138 Desktop.jalviewClipboard = new Object[] { seqs, ds, // what is the dataset
1140 // sequence ? need to
1142 // sequence as special.
1153 public void paintComponent(Graphics g)
1156 int width = getWidth();
1159 width = ap.calculateIdWidth().width;
1162 Graphics2D g2 = (Graphics2D) g;
1165 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
1166 RenderingHints.VALUE_ANTIALIAS_ON);
1169 drawComponent(g2, true, width);
1174 * Draw the full set of annotation Labels for the alignment at the given
1178 * Graphics2D instance (needed for font scaling)
1180 * Width for scaling labels
1183 public void drawComponent(Graphics g, int width)
1185 drawComponent(g, false, width);
1189 * Draw the full set of annotation Labels for the alignment at the given
1193 * Graphics2D instance (needed for font scaling)
1195 * - true indicates that only current visible area needs to be
1198 * Width for scaling labels
1200 public void drawComponent(Graphics g, boolean clip, int givenWidth)
1202 int width = givenWidth;
1203 IdwidthAdjuster iwa = null;
1206 iwa = ap.idwidthAdjuster;
1207 if ((Cache.getDefault(ADJUST_ANNOTATION_LABELS_WIDTH_PREF, true)
1208 || Jalview.isHeadlessMode()))
1210 Graphics2D g2d = (Graphics2D) g;
1211 Graphics dummy = g2d.create();
1212 int newAnnotationIdWidth = drawLabels(dummy, clip, width, false,
1215 Dimension d = ap.calculateDefaultAlignmentIdWidth();
1216 int alignmentIdWidth = d.width;
1217 if (iwa != null && !iwa.manuallyAdjusted())
1219 // If no manual adjustment to ID column with has been made then adjust
1220 // width match widest of alignment or annotation id widths
1221 boolean allowShrink = Cache.getDefault("ALLOW_SHRINK_ID_WIDTH",
1223 width = Math.max(alignmentIdWidth, newAnnotationIdWidth);
1224 if (clip && width < givenWidth && !allowShrink)
1229 else if (newAnnotationIdWidth != annotationIdWidth
1230 && newAnnotationIdWidth > givenWidth
1231 && newAnnotationIdWidth > alignmentIdWidth)
1233 // otherwise if the annotation id width has become larger than the
1234 // current id width, increase
1235 width = newAnnotationIdWidth;
1236 annotationIdWidth = newAnnotationIdWidth;
1238 // set the width if it's changed
1239 if (width != ap.av.getIdWidth())
1241 iwa.setWidth(width);
1247 Graphics2D g2d = (Graphics2D) g;
1248 Graphics dummy = g2d.create();
1249 int newAnnotationIdWidth = drawLabels(dummy, clip, width, false,
1251 width = Math.max(newAnnotationIdWidth, givenWidth);
1253 drawLabels(g, clip, width, true, null);
1257 * Render the full set of annotation Labels for the alignment at the given
1258 * cursor. If actuallyDraw is false or g is null then no actual drawing will
1259 * occur, but the widest label width will be returned. If g is null then
1260 * fmetrics must be supplied.
1262 * Returns the width of the annotation labels.
1265 * Graphics2D instance (needed for font scaling)
1267 * - true indicates that only current visible area needs to be
1270 * Width for scaling labels
1272 * FontMetrics if Graphics object g is null
1274 public int drawLabels(Graphics g, boolean clip, int width,
1275 boolean actuallyDraw, FontMetrics fmetrics)
1279 clip = Cache.getDefault("MOVE_SEQUENCE_ID_WITH_VISIBLE_ANNOTATIONS",
1282 int actualWidth = 0;
1285 if (av.getFont().getSize() < 10)
1291 g.setFont(av.getFont());
1295 FontMetrics fm = fmetrics == null ? g.getFontMetrics(g.getFont())
1299 g.setColor(Color.white);
1300 g.fillRect(0, 0, getWidth(), getHeight());
1305 g.translate(0, getScrollOffset());
1306 g.setColor(Color.black);
1308 SequenceI lastSeqRef = null;
1309 String lastLabel = null;
1310 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
1311 int fontHeight = g != null ? g.getFont().getSize()
1312 : fm.getFont().getSize();
1315 int graphExtras = 0;
1317 Font baseFont = g != null ? g.getFont() : fm.getFont();
1318 FontMetrics baseMetrics = fm;
1319 int ofontH = fontHeight;
1322 int[] visr = (ap != null && ap.getAnnotationPanel() != null)
1323 ? ap.getAnnotationPanel().getVisibleVRange()
1325 if (clip && visr != null)
1328 visHeight = visr[1];
1330 boolean visible = true, before = false, after = false;
1333 hasHiddenRows = false;
1336 for (int i = 0; i < aa.length; i++)
1341 hasHiddenRows = true;
1345 // look ahead to next annotation
1346 for (nexAA = i + 1; nexAA < aa.length
1347 && !aa[nexAA].visible; nexAA++)
1358 System.out.println("before vis: " + i);
1362 // don't draw what isn't visible
1365 if (olY > visHeight)
1373 "Scroll offset: " + sOffset + " after vis: " + i);
1377 // don't draw what isn't visible
1381 if (actuallyDraw && g != null)
1383 g.setColor(Color.black);
1385 offset = -aa[i].height / 2;
1389 offset += fm.getHeight() / 2;
1390 offset -= fm.getDescent();
1394 offset += fm.getDescent();
1396 String label = aa[i].label;
1397 boolean vertBar = false;
1398 if ((lastLabel != null && lastLabel.equals(label)))
1400 label = aa[i].description;
1404 if (nexAA < aa.length && label.equals(aa[nexAA].label)) // &&
1405 // aa[nexY].sequenceRef==aa[i].sequenceRef)
1408 // next label is the same as this label
1409 label = aa[i].description;
1416 if (aa[i].sequenceRef != null)
1418 if (aa[i].sequenceRef != lastSeqRef)
1420 label = aa[i].sequenceRef.getName() + " " + label;
1421 // TODO record relationship between sequence and this annotation and
1430 int labelWidth = fm.stringWidth(label) + 3;
1431 x = width - labelWidth;
1433 if (aa[i].graphGroup > -1)
1436 // TODO: JAL-1291 revise rendering model so the graphGroup map is
1437 // computed efficiently for all visible labels
1438 for (int gg = 0; gg < aa.length; gg++)
1440 if (aa[gg].graphGroup == aa[i].graphGroup)
1445 if (groupSize * (fontHeight + 8) < aa[i].height)
1447 graphExtras = (aa[i].height - (groupSize * (fontHeight + 8)))
1452 // scale font to fit
1453 float h = aa[i].height / (float) groupSize, s;
1460 fontHeight = -8 + (int) h;
1461 s = ((float) fontHeight) / (float) ofontH;
1463 .deriveFont(AffineTransform.getScaleInstance(s, s));
1464 Canvas c = new Canvas();
1465 fm = c.getFontMetrics(f);
1466 if (actuallyDraw && g != null)
1469 // fm = g.getFontMetrics();
1470 graphExtras = (aa[i].height
1471 - (groupSize * (fontHeight + 8))) / 2;
1477 for (int gg = 0; gg < aa.length; gg++)
1479 if (aa[gg].graphGroup == aa[i].graphGroup)
1481 labelWidth = fm.stringWidth(aa[gg].label) + 3;
1482 x = width - labelWidth;
1483 if (actuallyDraw && g != null)
1485 g.drawString(aa[gg].label, x, y - graphExtras);
1487 if (aa[gg]._linecolour != null)
1490 g.setColor(aa[gg]._linecolour);
1491 g.drawLine(x, y - graphExtras + 3,
1492 x + fm.stringWidth(aa[gg].label),
1493 y - graphExtras + 3);
1496 g.setColor(Color.black);
1498 graphExtras += fontHeight + 8;
1502 if (actuallyDraw && g != null)
1504 g.setFont(baseFont);
1507 fontHeight = ofontH;
1511 if (actuallyDraw && g != null)
1515 g.drawLine(width - 3, y + offset - fontHeight, width - 3,
1516 (int) (y - 1.5 * aa[i].height - offset - fontHeight));
1517 // g.drawLine(20, y + offset, x - 20, y + offset);
1520 g.drawString(label, x, y + offset);
1523 lastSeqRef = aa[i].sequenceRef;
1525 if (labelWidth > actualWidth)
1527 actualWidth = labelWidth;
1532 if (!resizePanel && dragEvent != null && aa != null)
1534 if (actuallyDraw && g != null)
1536 g.setColor(Color.lightGray);
1538 (aa[selectedRow].sequenceRef == null ? ""
1539 : aa[selectedRow].sequenceRef.getName())
1540 + aa[selectedRow].label,
1541 dragEvent.getX(), dragEvent.getY() - getScrollOffset());
1545 if (!av.getWrapAlignment() && ((aa == null) || (aa.length < 1)))
1547 if (actuallyDraw && g != null)
1549 g.drawString(MessageManager.getString("label.right_click"), 2, 8);
1550 g.drawString(MessageManager.getString("label.to_add_annotation"), 2,
1558 public int getScrollOffset()
1560 return scrollOffset;
1564 public void mouseEntered(MouseEvent e)