Clover icon

Coverage Report

  1. Project Clover database Thu Dec 4 2025 16:11:35 GMT
  2. Package jalview.gui

File AnnotationLabels.java

 

Coverage histogram

../../img/srcFileCovDistChart4.png
49% of files have more coverage

Code metrics

318
646
52
1
1,915
1,499
263
0.41
12.42
52
5.06

Classes

Class Line # Actions
AnnotationLabels 100 646 263
0.373031537.3%
 

Contributing tests

This file is covered by 236 tests. .

Source view

1    /*
2    * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3    * Copyright (C) $$Year-Rel$$ The Jalview Authors
4    *
5    * This file is part of Jalview.
6    *
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.
11    *
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.
16    *
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.
20    */
21    package jalview.gui;
22   
23    import java.util.Locale;
24   
25    import jalview.analysis.AlignSeq;
26    import jalview.analysis.AlignmentUtils;
27    import jalview.datamodel.Alignment;
28    import jalview.datamodel.AlignmentAnnotation;
29    import jalview.datamodel.Annotation;
30    import jalview.datamodel.HiddenColumns;
31    import jalview.datamodel.Sequence;
32    import jalview.datamodel.SequenceGroup;
33    import jalview.datamodel.SequenceI;
34    import jalview.io.FileFormat;
35    import jalview.io.FormatAdapter;
36    import jalview.util.Comparison;
37    import jalview.util.MessageManager;
38    import jalview.util.Platform;
39    import jalview.workers.InformationThread;
40   
41    import java.awt.Canvas;
42    import java.awt.Color;
43    import java.awt.Cursor;
44    import java.awt.Dimension;
45    import java.awt.Font;
46    import java.awt.FontMetrics;
47    import java.awt.Graphics;
48    import java.awt.Graphics2D;
49    import java.awt.RenderingHints;
50    import java.awt.Toolkit;
51    import java.awt.datatransfer.StringSelection;
52    import java.awt.event.ActionEvent;
53    import java.awt.event.ActionListener;
54    import java.awt.event.MouseEvent;
55    import java.awt.event.MouseListener;
56    import java.awt.event.MouseMotionListener;
57    import java.awt.geom.AffineTransform;
58    import java.util.Arrays;
59    import java.util.Collections;
60    import java.util.Iterator;
61    import java.util.List;
62    import java.util.Locale;
63   
64    import javax.swing.JCheckBoxMenuItem;
65    import javax.swing.JMenuItem;
66    import javax.swing.JPanel;
67    import javax.swing.JPopupMenu;
68    import javax.swing.SwingUtilities;
69    import javax.swing.ToolTipManager;
70   
71    import jalview.analysis.AlignSeq;
72    import jalview.analysis.AlignmentAnnotationUtils;
73    import jalview.analysis.AlignmentUtils;
74    import jalview.api.AlignCalcWorkerI;
75    import jalview.bin.Cache;
76    import jalview.bin.Jalview;
77    import jalview.datamodel.Alignment;
78    import jalview.datamodel.AlignmentAnnotation;
79    import jalview.datamodel.Annotation;
80    import jalview.datamodel.ContactMatrixI;
81    import jalview.datamodel.GroupSet;
82    import jalview.datamodel.HiddenColumns;
83    import jalview.datamodel.Sequence;
84    import jalview.datamodel.SequenceGroup;
85    import jalview.datamodel.SequenceI;
86    import jalview.io.FileFormat;
87    import jalview.io.FormatAdapter;
88    import jalview.util.Comparison;
89    import jalview.util.Constants;
90    import jalview.util.MessageManager;
91    import jalview.util.ParseHtmlBodyAndLinks;
92    import jalview.util.Platform;
93    import jalview.workers.SecondaryStructureConsensusThread;
94   
95    /**
96    * The panel that holds the labels for alignment annotations, providing
97    * tooltips, context menus, drag to reorder rows, and drag to adjust panel
98    * height
99    */
 
100    public class AnnotationLabels extends JPanel
101    implements MouseListener, MouseMotionListener, ActionListener
102    {
103    private static final String HTML_END_TAG = "</html>";
104   
105    private static final String HTML_START_TAG = "<html>";
106   
107    /**
108    * width in pixels within which height adjuster arrows are shown and active
109    */
110    private static final int HEIGHT_ADJUSTER_WIDTH = 50;
111   
112    /**
113    * height in pixels for allowing height adjuster to be active
114    */
115    public static int HEIGHT_ADJUSTER_HEIGHT = 10;
116   
117    private static final Font font = new Font("Arial", Font.PLAIN, 11);
118   
119    private static final String TOGGLE_LABELSCALE = MessageManager
120    .getString("label.scale_label_to_column");
121   
122    private static final String ADDNEW = MessageManager
123    .getString("label.add_new_row");
124   
125    private static final String EDITNAME = MessageManager
126    .getString("label.edit_label_description");
127   
128    private static final String HIDE = MessageManager
129    .getString("label.hide_row");
130   
131    private static final String DELETE = MessageManager
132    .getString("label.delete_row");
133   
134    private static final String SHOWALL = MessageManager
135    .getString("label.show_all_hidden_rows");
136   
137    private static final String OUTPUT_TEXT = MessageManager
138    .getString("label.export_annotation");
139   
140    private static final String COPYCONS_SEQ = MessageManager
141    .getString("label.copy_consensus_sequence");
142   
143    private static final String ADJUST_ANNOTATION_LABELS_WIDTH_PREF = "ADJUST_ANNOTATION_LABELS_WIDTH";
144   
145    private final boolean debugRedraw = false;
146   
147    private AlignmentPanel ap;
148   
149    AlignViewport av;
150   
151    private MouseEvent dragEvent;
152   
153    private int oldY;
154   
155    private int selectedRow;
156   
157    private int scrollOffset = 0;
158   
159    private boolean hasHiddenRows;
160   
161    private boolean resizePanel = false;
162   
163    private int annotationIdWidth = -1;
164   
165    public static final String RESIZE_MARGINS_MARK_PREF = "RESIZE_MARGINS_MARK";
166   
167    /**
168    * Creates a new AnnotationLabels object
169    *
170    * @param ap
171    */
 
172  513 toggle public AnnotationLabels(AlignmentPanel ap)
173    {
174  513 this.ap = ap;
175  513 av = ap.av;
176  513 ToolTipManager.sharedInstance().registerComponent(this);
177   
178  513 addMouseListener(this);
179  513 addMouseMotionListener(this);
180  513 addMouseWheelListener(ap.getAnnotationPanel());
181    }
182   
 
183  20 toggle public AnnotationLabels(AlignViewport av)
184    {
185  20 this.av = av;
186    }
187   
188    /**
189    * DOCUMENT ME!
190    *
191    * @param y
192    * DOCUMENT ME!
193    */
 
194  1046 toggle public void setScrollOffset(int y)
195    {
196  1046 scrollOffset = y;
197  1046 repaint();
198    }
199   
200    /**
201    * sets selectedRow to -2 if no annotation preset, -1 if no visible row is at
202    * y
203    *
204    * @param y
205    * coordinate position to search for a row
206    */
 
207  0 toggle void getSelectedRow(int y)
208    {
209  0 int height = 0;
210  0 AlignmentAnnotation[] aa = ap.av.getAlignment()
211    .getAlignmentAnnotation();
212  0 selectedRow = -2;
213  0 if (aa != null)
214    {
215  0 for (int i = 0; i < aa.length; i++)
216    {
217  0 selectedRow = -1;
218  0 if (!aa[i].isForDisplay())
219    {
220  0 continue;
221    }
222   
223  0 height += aa[i].height;
224   
225  0 if (y < height)
226    {
227  0 selectedRow = i;
228   
229  0 break;
230    }
231    }
232    }
233    }
234   
235    /**
236    * DOCUMENT ME!
237    *
238    * @param evt
239    * DOCUMENT ME!
240    */
 
241  0 toggle @Override
242    public void actionPerformed(ActionEvent evt)
243    {
244  0 AlignmentAnnotation[] aa = ap.av.getAlignment()
245    .getAlignmentAnnotation();
246   
247  0 String action = evt.getActionCommand();
248  0 if (ADDNEW.equals(action))
249    {
250    /*
251    * non-returning dialog
252    */
253  0 AlignmentAnnotation newAnnotation = new AlignmentAnnotation(null,
254    null, new Annotation[ap.av.getAlignment().getWidth()]);
255  0 editLabelDescription(newAnnotation, true);
256    }
257  0 else if (EDITNAME.equals(action))
258    {
259    /*
260    * non-returning dialog
261    */
262  0 editLabelDescription(aa[selectedRow], false);
263    }
264  0 else if (HIDE.equals(action))
265    {
266  0 aa[selectedRow].visible = false;
267    }
268  0 else if (DELETE.equals(action))
269    {
270  0 ap.av.getAlignment().deleteAnnotation(aa[selectedRow]);
271  0 ap.av.getCalcManager().removeWorkerForAnnotation(aa[selectedRow]);
272    }
273  0 else if (SHOWALL.equals(action))
274    {
275  0 for (int i = 0; i < aa.length; i++)
276    {
277  0 if (!aa[i].visible && aa[i].annotations != null)
278    {
279  0 aa[i].visible = true;
280    }
281    }
282    }
283  0 else if (OUTPUT_TEXT.equals(action))
284    {
285  0 new AnnotationExporter(ap).exportAnnotation(aa[selectedRow]);
286    }
287  0 else if (COPYCONS_SEQ.equals(action))
288    {
289  0 SequenceI cons = null;
290  0 if (aa[selectedRow].groupRef != null)
291    {
292  0 cons = aa[selectedRow].groupRef.getConsensusSeq();
293    }
294    else
295    {
296  0 cons = av.getConsensusSeq();
297    }
298  0 if (cons != null)
299    {
300  0 copy_annotseqtoclipboard(cons);
301    }
302    }
303  0 else if (TOGGLE_LABELSCALE.equals(action))
304    {
305  0 aa[selectedRow].scaleColLabel = !aa[selectedRow].scaleColLabel;
306    }
307   
308  0 ap.refresh(true);
309    }
310   
311    /**
312    * Shows a dialog where the annotation name and description may be edited. If
313    * parameter addNew is true, then on confirmation, a new AlignmentAnnotation
314    * is added, else an existing annotation is updated.
315    *
316    * @param annotation
317    * @param addNew
318    */
 
319  0 toggle void editLabelDescription(AlignmentAnnotation annotation, boolean addNew)
320    {
321  0 String name = MessageManager.getString("label.annotation_name");
322  0 String description = MessageManager
323    .getString("label.annotation_description");
324  0 String title = MessageManager
325    .getString("label.edit_annotation_name_description");
326  0 EditNameDialog dialog = new EditNameDialog(annotation.label,
327    annotation.description, name, description);
328   
329  0 dialog.showDialog(ap.alignFrame, title, () -> {
330  0 annotation.label = dialog.getName();
331  0 String text = dialog.getDescription();
332  0 if (text != null && text.length() == 0)
333    {
334  0 text = null;
335    }
336  0 annotation.description = text;
337  0 if (addNew)
338    {
339  0 ap.av.getAlignment().addAnnotation(annotation);
340  0 ap.av.getAlignment().setAnnotationIndex(annotation, 0);
341    }
342  0 ap.refresh(true);
343    });
344    }
345   
 
346  0 toggle @Override
347    public void mousePressed(MouseEvent evt)
348    {
349  0 getSelectedRow(evt.getY() - getScrollOffset());
350  0 oldY = evt.getY();
351  0 if (evt.isPopupTrigger())
352    {
353  0 showPopupMenu(evt);
354    }
355    }
356   
357    /**
358    * Build and show the Pop-up menu at the right-click mouse position
359    *
360    * @param evt
361    */
 
362  0 toggle void showPopupMenu(MouseEvent evt)
363    {
364  0 evt.consume();
365  0 final AlignmentAnnotation[] aa = ap.av.getAlignment()
366    .getAlignmentAnnotation();
367   
368  0 JPopupMenu pop = new JPopupMenu(
369    MessageManager.getString("label.annotations"));
370  0 JMenuItem item = new JMenuItem(ADDNEW);
371  0 item.addActionListener(this);
372  0 pop.add(item);
373  0 if (selectedRow < 0)
374    {
375  0 if (hasHiddenRows)
376    { // let the user make everything visible again
377  0 item = new JMenuItem(SHOWALL);
378  0 item.addActionListener(this);
379  0 pop.add(item);
380    }
381  0 pop.show(this, evt.getX(), evt.getY());
382  0 return;
383    }
384  0 final AlignmentAnnotation ann = aa[selectedRow];
385  0 final boolean isSequenceAnnotation = ann.sequenceRef != null;
386  0 item = new JMenuItem(EDITNAME);
387  0 item.addActionListener(this);
388  0 pop.add(item);
389  0 item = new JMenuItem(HIDE);
390  0 item.addActionListener(this);
391  0 pop.add(item);
392    // JAL-1264 hide all sequence-specific annotations of this type
393  0 if (selectedRow < aa.length)
394    {
395  0 if (aa[selectedRow].sequenceRef != null)
396    {
397  0 final String label = aa[selectedRow].label;
398  0 JMenuItem hideType = new JMenuItem();
399  0 String text = MessageManager.getString("label.hide_all") + " "
400    + label;
401  0 hideType.setText(text);
402  0 hideType.addActionListener(new ActionListener()
403    {
 
404  0 toggle @Override
405    public void actionPerformed(ActionEvent e)
406    {
407  0 AlignmentUtils.showOrHideSequenceAnnotations(
408    ap.av.getAlignment(), Collections.singleton(label),
409    null, false, false);
410  0 ap.updateAnnotation();
411  0 ap.refresh(true);
412    }
413    });
414  0 pop.add(hideType);
415    }
416    }
417  0 item = new JMenuItem(DELETE);
418  0 item.addActionListener(this);
419  0 pop.add(item);
420  0 if (hasHiddenRows)
421    {
422  0 item = new JMenuItem(SHOWALL);
423  0 item.addActionListener(this);
424  0 pop.add(item);
425    }
426  0 item = new JMenuItem(OUTPUT_TEXT);
427  0 item.addActionListener(this);
428  0 pop.add(item);
429    // TODO: annotation object should be typed for autocalculated/derived
430    // property methods
431  0 if (selectedRow < aa.length)
432    {
433  0 final String label = aa[selectedRow].label;
434  0 if (!(aa[selectedRow].autoCalculated)
435    && !(InformationThread.HMM_CALC_ID.equals(ann.getCalcId())))
436    {
437  0 if (aa[selectedRow].graph == AlignmentAnnotation.NO_GRAPH)
438    {
439    // display formatting settings for this row.
440  0 pop.addSeparator();
441    // av and sequencegroup need to implement same interface for
442  0 item = new JCheckBoxMenuItem(TOGGLE_LABELSCALE,
443    aa[selectedRow].scaleColLabel);
444  0 item.addActionListener(this);
445  0 pop.add(item);
446    }
447    }
448  0 else if (label.indexOf("Consensus") > -1)
449    {
450  0 addConsensusMenuOptions(ap, aa[selectedRow], pop);
451   
452  0 final JMenuItem consclipbrd = new JMenuItem(COPYCONS_SEQ);
453  0 consclipbrd.addActionListener(this);
454  0 pop.add(consclipbrd);
455    }
456   
457  0 addColourOrFilterByOptions(ap, aa[selectedRow], pop);
458   
459  0 if (aa[selectedRow].graph == AlignmentAnnotation.CONTACT_MAP)
460    {
461  0 addContactMatrixOptions(ap, aa[selectedRow], pop);
462    // Set/adjust threshold for grouping ?
463    // colour alignment by this [type]
464    // select/hide columns by this row
465   
466    }
467  0 else if (InformationThread.HMM_CALC_ID.equals(ann.getCalcId()))
468    {
469  0 addHmmerMenu(pop, ann);
470    }
471    }
472   
473  0 pop.show(this, evt.getX(), evt.getY());
474    }
475   
 
476  0 toggle static void addColourOrFilterByOptions(final AlignmentPanel ap,
477    final AlignmentAnnotation alignmentAnnotation,
478    final JPopupMenu pop)
479    {
480  0 JMenuItem item;
481  0 item = new JMenuItem(
482    MessageManager.getString("label.colour_by_annotation"));
483  0 item.addActionListener(new ActionListener()
484    {
485   
 
486  0 toggle @Override
487    public void actionPerformed(ActionEvent e)
488    {
489  0 AnnotationColourChooser.displayFor(ap.av, ap, alignmentAnnotation,
490    false);
491    };
492    });
493  0 pop.add(item);
494  0 if (alignmentAnnotation.sequenceRef != null)
495    {
496  0 item = new JMenuItem(
497    MessageManager.getString("label.colour_by_annotation") + " ("
498    + MessageManager.getString("label.per_seq") + ")");
499  0 item.addActionListener(new ActionListener()
500    {
 
501  0 toggle @Override
502    public void actionPerformed(ActionEvent e)
503    {
504  0 AnnotationColourChooser.displayFor(ap.av, ap, alignmentAnnotation,
505    true);
506    };
507    });
508  0 pop.add(item);
509    }
510  0 item = new JMenuItem(
511    MessageManager.getString("action.select_by_annotation"));
512  0 item.addActionListener(new ActionListener()
513    {
514   
 
515  0 toggle @Override
516    public void actionPerformed(ActionEvent e)
517    {
518  0 AnnotationColumnChooser.displayFor(ap.av, ap, alignmentAnnotation);
519    };
520    });
521  0 pop.add(item);
522    }
523   
 
524  0 toggle static void addContactMatrixOptions(final AlignmentPanel ap,
525    final AlignmentAnnotation alignmentAnnotation,
526    final JPopupMenu pop)
527    {
528   
529  0 final ContactMatrixI cm = ap.av.getContactMatrix(alignmentAnnotation);
530  0 JMenuItem item;
531  0 if (cm != null)
532    {
533  0 pop.addSeparator();
534   
535  0 if (cm.hasGroups())
536    {
537  0 JCheckBoxMenuItem chitem = new JCheckBoxMenuItem(
538    MessageManager.getString("action.show_groups_on_matrix"));
539  0 chitem.setToolTipText(MessageManager
540    .getString("action.show_groups_on_matrix_tooltip"));
541  0 boolean showGroups = alignmentAnnotation
542    .isShowGroupsForContactMatrix();
543  0 final AlignmentAnnotation sel_row = alignmentAnnotation;
544  0 chitem.setState(showGroups);
545  0 chitem.addActionListener(new ActionListener()
546    {
547   
 
548  0 toggle @Override
549    public void actionPerformed(ActionEvent e)
550    {
551  0 sel_row.setShowGroupsForContactMatrix(chitem.getState());
552    // so any annotation colour changes are propagated - though they
553    // probably won't be unless the annotation row colours are removed
554    // too!
555  0 ap.alignmentChanged();
556    }
557    });
558  0 pop.add(chitem);
559    }
560  0 if (cm.hasTree())
561    {
562  0 item = new JMenuItem(
563    MessageManager.getString("action.show_tree_for_matrix"));
564  0 item.setToolTipText(MessageManager
565    .getString("action.show_tree_for_matrix_tooltip"));
566  0 item.addActionListener(new ActionListener()
567    {
568   
 
569  0 toggle @Override
570    public void actionPerformed(ActionEvent e)
571    {
572   
573  0 ap.alignFrame.showContactMapTree(alignmentAnnotation, cm);
574   
575    }
576    });
577  0 pop.add(item);
578    }
579    else
580    {
581  0 item = new JMenuItem(
582    MessageManager.getString("action.cluster_matrix"));
583  0 item.setToolTipText(
584    MessageManager.getString("action.cluster_matrix_tooltip"));
585  0 item.addActionListener(new ActionListener()
586    {
 
587  0 toggle @Override
588    public void actionPerformed(ActionEvent e)
589    {
590  0 new Thread(new Runnable()
591    {
 
592  0 toggle @Override
593    public void run()
594    {
595  0 final long progBar;
596  0 ap.alignFrame.setProgressBar(
597    MessageManager.formatMessage(
598    "action.clustering_matrix_for",
599    cm.getAnnotDescr(), 5f),
600    progBar = System.currentTimeMillis());
601  0 cm.setGroupSet(GroupSet.makeGroups(cm, true));
602  0 cm.randomlyReColourGroups();
603  0 cm.transferGroupColorsTo(alignmentAnnotation);
604  0 ap.alignmentChanged();
605  0 ap.alignFrame.showContactMapTree(alignmentAnnotation, cm);
606  0 ap.alignFrame.setProgressBar(null, progBar);
607    }
608    }).start();
609    }
610    });
611  0 pop.add(item);
612    }
613    }
614    }
615   
616    /**
617    * Adds context menu options for (alignment or group) Hmmer annotation
618    *
619    * @param pop
620    * @param ann
621    */
 
622  0 toggle protected void addHmmerMenu(JPopupMenu pop, final AlignmentAnnotation ann)
623    {
624  0 final boolean isGroupAnnotation = ann.groupRef != null;
625  0 pop.addSeparator();
626  0 final JCheckBoxMenuItem cbmi = new JCheckBoxMenuItem(
627    MessageManager.getString(
628    "label.ignore_below_background_frequency"),
629  0 isGroupAnnotation
630    ? ann.groupRef
631    .isIgnoreBelowBackground()
632    : ap.av.isIgnoreBelowBackground());
633  0 cbmi.addActionListener(new ActionListener()
634    {
 
635  0 toggle @Override
636    public void actionPerformed(ActionEvent e)
637    {
638  0 if (isGroupAnnotation)
639    {
640  0 if (!ann.groupRef.isUseInfoLetterHeight())
641    {
642  0 ann.groupRef.setIgnoreBelowBackground(cbmi.getState());
643    // todo and recompute group annotation
644    }
645    }
646  0 else if (!ap.av.isInfoLetterHeight())
647    {
648  0 ap.av.setIgnoreBelowBackground(cbmi.getState(), ap);
649    // todo and recompute annotation
650    }
651  0 ap.alignmentChanged(); // todo not like this
652    }
653    });
654  0 pop.add(cbmi);
655  0 final JCheckBoxMenuItem letterHeight = new JCheckBoxMenuItem(
656    MessageManager.getString("label.use_info_for_height"),
657  0 isGroupAnnotation ? ann.groupRef.isUseInfoLetterHeight()
658    : ap.av.isInfoLetterHeight());
659  0 letterHeight.addActionListener(new ActionListener()
660    {
 
661  0 toggle @Override
662    public void actionPerformed(ActionEvent e)
663    {
664  0 if (isGroupAnnotation)
665    {
666  0 ann.groupRef.setInfoLetterHeight((letterHeight.getState()));
667  0 ann.groupRef.setIgnoreBelowBackground(true);
668    // todo and recompute group annotation
669    }
670    else
671    {
672  0 ap.av.setInfoLetterHeight(letterHeight.getState(), ap);
673  0 ap.av.setIgnoreBelowBackground(true, ap);
674    // todo and recompute annotation
675    }
676  0 ap.alignmentChanged();
677    }
678    });
679  0 pop.add(letterHeight);
680  0 if (isGroupAnnotation)
681    {
682  0 final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
683    MessageManager.getString("label.show_group_histogram"),
684    ann.groupRef.isShowInformationHistogram());
685  0 chist.addActionListener(new ActionListener()
686    {
 
687  0 toggle @Override
688    public void actionPerformed(ActionEvent e)
689    {
690  0 ann.groupRef.setShowInformationHistogram(chist.getState());
691  0 ap.repaint();
692    }
693    });
694  0 pop.add(chist);
695  0 final JCheckBoxMenuItem cprofl = new JCheckBoxMenuItem(
696    MessageManager.getString("label.show_group_logo"),
697    ann.groupRef.isShowHMMSequenceLogo());
698  0 cprofl.addActionListener(new ActionListener()
699    {
 
700  0 toggle @Override
701    public void actionPerformed(ActionEvent e)
702    {
703  0 ann.groupRef.setShowHMMSequenceLogo(cprofl.getState());
704  0 ap.repaint();
705    }
706    });
707  0 pop.add(cprofl);
708  0 final JCheckBoxMenuItem cproflnorm = new JCheckBoxMenuItem(
709    MessageManager.getString("label.normalise_group_logo"),
710    ann.groupRef.isNormaliseHMMSequenceLogo());
711  0 cproflnorm.addActionListener(new ActionListener()
712    {
 
713  0 toggle @Override
714    public void actionPerformed(ActionEvent e)
715    {
716  0 ann.groupRef
717    .setNormaliseHMMSequenceLogo(cproflnorm.getState());
718    // automatically enable logo display if we're clicked
719  0 ann.groupRef.setShowHMMSequenceLogo(true);
720  0 ap.repaint();
721    }
722    });
723  0 pop.add(cproflnorm);
724    }
725    else
726    {
727  0 final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
728    MessageManager.getString("label.show_histogram"),
729    av.isShowInformationHistogram());
730  0 chist.addActionListener(new ActionListener()
731    {
 
732  0 toggle @Override
733    public void actionPerformed(ActionEvent e)
734    {
735  0 av.setShowInformationHistogram(chist.getState());
736  0 ap.repaint();
737    }
738    });
739  0 pop.add(chist);
740  0 final JCheckBoxMenuItem cprof = new JCheckBoxMenuItem(
741    MessageManager.getString("label.show_logo"),
742    av.isShowHMMSequenceLogo());
743  0 cprof.addActionListener(new ActionListener()
744    {
 
745  0 toggle @Override
746    public void actionPerformed(ActionEvent e)
747    {
748  0 av.setShowHMMSequenceLogo(cprof.getState());
749  0 ap.repaint();
750    }
751    });
752  0 pop.add(cprof);
753  0 final JCheckBoxMenuItem cprofnorm = new JCheckBoxMenuItem(
754    MessageManager.getString("label.normalise_logo"),
755    av.isNormaliseHMMSequenceLogo());
756  0 cprofnorm.addActionListener(new ActionListener()
757    {
 
758  0 toggle @Override
759    public void actionPerformed(ActionEvent e)
760    {
761  0 av.setShowHMMSequenceLogo(true);
762  0 av.setNormaliseHMMSequenceLogo(cprofnorm.getState());
763  0 ap.repaint();
764    }
765    });
766  0 pop.add(cprofnorm);
767    }
768    }
769   
770    /**
771    * A helper method that adds menu options for calculation and visualisation of
772    * group and/or alignment consensus annotation to a popup menu. This is
773    * designed to be reusable for either unwrapped mode (popup menu is shown on
774    * component AnnotationLabels), or wrapped mode (popup menu is shown on
775    * IdPanel when the mouse is over an annotation label).
776    *
777    * @param ap
778    * @param ann
779    * @param pop
780    */
 
781  0 toggle static void addConsensusMenuOptions(AlignmentPanel ap,
782    AlignmentAnnotation ann, JPopupMenu pop)
783    {
784  0 pop.addSeparator();
785   
786  0 final JCheckBoxMenuItem cbmi = new JCheckBoxMenuItem(
787    MessageManager.getString("label.ignore_gaps_consensus"),
788  0 (ann.groupRef != null) ? ann.groupRef.getIgnoreGapsConsensus()
789    : ap.av.isIgnoreGapsConsensus());
790  0 final AlignmentAnnotation aaa = ann;
791  0 cbmi.addActionListener(new ActionListener()
792    {
 
793  0 toggle @Override
794    public void actionPerformed(ActionEvent e)
795    {
796  0 if (aaa.groupRef != null)
797    {
798  0 aaa.groupRef.setIgnoreGapsConsensus(cbmi.getState());
799  0 ap.getAnnotationPanel()
800    .paint(ap.getAnnotationPanel().getGraphics());
801    }
802    else
803    {
804  0 ap.av.setIgnoreGapsConsensus(cbmi.getState(), ap);
805    }
806  0 ap.alignmentChanged();
807    }
808    });
809  0 pop.add(cbmi);
810   
811  0 if (aaa.groupRef != null)
812    {
813    /*
814    * group consensus options
815    */
816  0 final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
817    MessageManager.getString("label.show_group_histogram"),
818    ann.groupRef.isShowConsensusHistogram());
819  0 chist.addActionListener(new ActionListener()
820    {
 
821  0 toggle @Override
822    public void actionPerformed(ActionEvent e)
823    {
824  0 aaa.groupRef.setShowConsensusHistogram(chist.getState());
825  0 ap.repaint();
826    }
827    });
828  0 pop.add(chist);
829  0 final JCheckBoxMenuItem cprofl = new JCheckBoxMenuItem(
830    MessageManager.getString("label.show_group_logo"),
831    ann.groupRef.isShowSequenceLogo());
832  0 cprofl.addActionListener(new ActionListener()
833    {
 
834  0 toggle @Override
835    public void actionPerformed(ActionEvent e)
836    {
837  0 aaa.groupRef.setshowSequenceLogo(cprofl.getState());
838  0 ap.repaint();
839    }
840    });
841  0 pop.add(cprofl);
842  0 final JCheckBoxMenuItem cproflnorm = new JCheckBoxMenuItem(
843    MessageManager.getString("label.normalise_group_logo"),
844    ann.groupRef.isNormaliseSequenceLogo());
845  0 cproflnorm.addActionListener(new ActionListener()
846    {
 
847  0 toggle @Override
848    public void actionPerformed(ActionEvent e)
849    {
850  0 aaa.groupRef.setNormaliseSequenceLogo(cproflnorm.getState());
851    // automatically enable logo display if we're clicked
852  0 aaa.groupRef.setshowSequenceLogo(true);
853  0 ap.repaint();
854    }
855    });
856  0 pop.add(cproflnorm);
857    }
858    else
859    {
860    /*
861    * alignment consensus options
862    */
863  0 final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
864    MessageManager.getString("label.show_histogram"),
865    ap.av.isShowConsensusHistogram());
866  0 chist.addActionListener(new ActionListener()
867    {
 
868  0 toggle @Override
869    public void actionPerformed(ActionEvent e)
870    {
871  0 ap.av.setShowConsensusHistogram(chist.getState());
872  0 ap.alignFrame.setMenusForViewport();
873  0 ap.repaint();
874    }
875    });
876  0 pop.add(chist);
877  0 final JCheckBoxMenuItem cprof = new JCheckBoxMenuItem(
878    MessageManager.getString("label.show_logo"),
879    ap.av.isShowSequenceLogo());
880  0 cprof.addActionListener(new ActionListener()
881    {
 
882  0 toggle @Override
883    public void actionPerformed(ActionEvent e)
884    {
885  0 ap.av.setShowSequenceLogo(cprof.getState());
886  0 ap.alignFrame.setMenusForViewport();
887  0 ap.repaint();
888    }
889    });
890  0 pop.add(cprof);
891  0 final JCheckBoxMenuItem cprofnorm = new JCheckBoxMenuItem(
892    MessageManager.getString("label.normalise_logo"),
893    ap.av.isNormaliseSequenceLogo());
894  0 cprofnorm.addActionListener(new ActionListener()
895    {
 
896  0 toggle @Override
897    public void actionPerformed(ActionEvent e)
898    {
899  0 ap.av.setShowSequenceLogo(true);
900  0 ap.av.setNormaliseSequenceLogo(cprofnorm.getState());
901  0 ap.alignFrame.setMenusForViewport();
902  0 ap.repaint();
903    }
904    });
905  0 pop.add(cprofnorm);
906    }
907    }
908   
909    /**
910    * Reorders annotation rows after a drag of a label
911    *
912    * @param evt
913    */
 
914  0 toggle @Override
915    public void mouseReleased(MouseEvent evt)
916    {
917  0 if (evt.isPopupTrigger())
918    {
919  0 showPopupMenu(evt);
920  0 return;
921    }
922   
923  0 int start = selectedRow;
924  0 getSelectedRow(evt.getY() - getScrollOffset());
925  0 int end = selectedRow;
926   
927    /*
928    * if dragging to resize instead, start == end
929    */
930  0 if (start != end)
931    {
932    // Swap these annotations
933  0 AlignmentAnnotation startAA = ap.av.getAlignment()
934    .getAlignmentAnnotation()[start];
935  0 if (end == -1)
936    {
937  0 end = ap.av.getAlignment().getAlignmentAnnotation().length - 1;
938    }
939  0 AlignmentAnnotation endAA = ap.av.getAlignment()
940    .getAlignmentAnnotation()[end];
941   
942  0 ap.av.getAlignment().getAlignmentAnnotation()[end] = startAA;
943  0 ap.av.getAlignment().getAlignmentAnnotation()[start] = endAA;
944    }
945   
946  0 resizePanel = false;
947  0 dragEvent = null;
948  0 repaint();
949  0 ap.getAnnotationPanel().repaint();
950    }
951   
952    /**
953    * Removes the height adjuster image on leaving the panel, unless currently
954    * dragging it
955    */
 
956  0 toggle @Override
957    public void mouseExited(MouseEvent evt)
958    {
959  0 if (resizePanel && dragEvent == null)
960    {
961  0 resizePanel = false;
962  0 repaint();
963    }
964    }
965   
966    /**
967    * A mouse drag may be either an adjustment of the panel height (if flag
968    * resizePanel is set on), or a reordering of the annotation rows. The former
969    * is dealt with by this method, the latter in mouseReleased.
970    *
971    * @param evt
972    */
 
973  0 toggle @Override
974    public void mouseDragged(MouseEvent evt)
975    {
976  0 dragEvent = evt;
977   
978  0 if (resizePanel)
979    {
980  0 Dimension d = ap.annotationScroller.getPreferredSize();
981  0 int dif = evt.getY() - oldY;
982  0 dif -= dif % ap.av.getCharHeight();
983   
984    // don't allow setting an annotation panel height larger than visible
985    // (otherwise you can't get back)
986  0 if (d.height - dif > ap.idPanelHolder.getHeight()
987    - ap.getIdSpaceFillerPanel1().getHeight())
988    {
989  0 return;
990    }
991   
992  0 if ((d.height - dif) > 20)
993    {
994  0 ap.annotationScroller
995    .setPreferredSize(new Dimension(d.width, d.height - dif));
996  0 d = ap.annotationSpaceFillerHolder.getPreferredSize();
997  0 ap.annotationSpaceFillerHolder
998    .setPreferredSize(new Dimension(d.width, d.height - dif));
999  0 ap.paintAlignment(true, false);
1000    }
1001   
1002  0 ap.addNotify();
1003    }
1004    else
1005    {
1006  0 repaint();
1007    }
1008    }
1009   
1010    /**
1011    * Updates the tooltip as the mouse moves over the labels
1012    *
1013    * @param evt
1014    */
 
1015  0 toggle @Override
1016    public void mouseMoved(MouseEvent evt)
1017    {
1018  0 showOrHideAdjuster(evt);
1019   
1020  0 getSelectedRow(evt.getY() - getScrollOffset());
1021   
1022  0 if (selectedRow > -1 && ap.av.getAlignment()
1023    .getAlignmentAnnotation().length > selectedRow)
1024    {
1025  0 AlignmentAnnotation[] anns = ap.av.getAlignment()
1026    .getAlignmentAnnotation();
1027  0 AlignmentAnnotation aa = anns[selectedRow];
1028   
1029  0 String desc = getTooltip(aa);
1030  0 this.setToolTipText(desc);
1031  0 String msg = getStatusMessage(aa, anns);
1032  0 ap.alignFrame.setStatus(msg);
1033    }
1034    }
1035   
1036    /**
1037    * Constructs suitable text to show in the status bar when over an annotation
1038    * label, containing the associated sequence name (if any), and the annotation
1039    * labels (or all labels for a graph group annotation)
1040    *
1041    * @param aa
1042    * @param anns
1043    * @return
1044    */
 
1045  6 toggle static String getStatusMessage(AlignmentAnnotation aa,
1046    AlignmentAnnotation[] anns)
1047    {
1048  6 if (aa == null)
1049    {
1050  1 return null;
1051    }
1052   
1053  5 StringBuilder msg = new StringBuilder(32);
1054  5 if (aa.sequenceRef != null)
1055    {
1056  3 msg.append(aa.sequenceRef.getName()).append(" : ");
1057    }
1058   
1059  5 if (aa.graphGroup == -1)
1060    {
1061  2 msg.append(aa.label);
1062  2 addNumbersOfSeqsAndTracks(msg,aa);
1063    }
1064   
1065  3 else if (anns != null)
1066    {
1067  3 boolean first = true;
1068  9 for (int i = anns.length - 1; i >= 0; i--)
1069    {
1070  6 if (anns[i].graphGroup == aa.graphGroup)
1071    {
1072  5 if (!first)
1073    {
1074  2 msg.append(", ");
1075    }
1076  5 msg.append(anns[i].label);
1077  5 first = false;
1078    }
1079    }
1080    }
1081   
1082  5 return msg.toString();
1083    }
1084   
 
1085  12 toggle private static StringBuilder addNumbersOfSeqsAndTracks(StringBuilder sb, AlignmentAnnotation aa)
1086    {
1087  12 boolean closebracket=false;
1088  12 if (aa.getNoOfSequencesIncluded() > 0)
1089    {
1090  0 closebracket=true;
1091  0 sb.append(" (").append(
1092    MessageManager.getString("label.sequence_count")).append(aa.getNoOfSequencesIncluded());
1093    }
1094   
1095  12 if (aa.getNoOfTracksIncluded() > 0)
1096    {
1097  0 if (!closebracket) {
1098  0 sb.append(" (");
1099  0 closebracket=true;
1100    } else {
1101  0 sb.append(", ");
1102    }
1103  0 sb.append(MessageManager.getString("label.tracks_count")).append(
1104    aa.getNoOfTracksIncluded());
1105    }
1106   
1107  12 if (closebracket)
1108    {
1109  0 sb.append(")");
1110    }
1111  12 return sb;
1112    }
1113    /**
1114    * Answers a tooltip, formatted as html, containing the annotation description
1115    * (prefixed by associated sequence id if applicable), and the annotation
1116    * (non-positional) score if it has one. Answers null if neither description
1117    * nor score is found.
1118    *
1119    * @param aa
1120    * @return
1121    */
 
1122  13 toggle static String getTooltip(AlignmentAnnotation aa)
1123    {
1124  13 if (aa == null)
1125    {
1126  1 return null;
1127    }
1128  12 StringBuilder tooltip = new StringBuilder();
1129  12 if (aa.description != null && !aa.description.equals("New description"))
1130    {
1131    // TODO: we could refactor and merge this code with the code in
1132    // jalview.gui.SeqPanel.mouseMoved(..) that formats sequence feature
1133    // tooltips
1134  10 String desc = aa.getDescription(true).trim();
1135   
1136    // recycle tooltip stringbuilder here - see TODO below
1137  10 desc += addNumbersOfSeqsAndTracks(tooltip, aa).toString();
1138  10 tooltip.setLength(0);
1139   
1140    // TODO change this HTMLisation of desc to use a string builder instead ?
1141   
1142  10 if (!desc.toLowerCase(Locale.ROOT).startsWith(HTML_START_TAG))
1143    {
1144  7 tooltip.append(HTML_START_TAG);
1145  7 desc = desc.replace("<", "&lt;");
1146    }
1147  3 else if (desc.toLowerCase(Locale.ROOT).endsWith(HTML_END_TAG))
1148    {
1149  3 desc = desc.substring(0, desc.length() - HTML_END_TAG.length());
1150    }
1151  10 tooltip.append(desc);
1152    }
1153    else
1154    {
1155    // begin the tooltip's html fragment
1156  2 tooltip.append(HTML_START_TAG);
1157    }
1158  12 if (aa.hasScore())
1159    {
1160  5 if (tooltip.length() > HTML_START_TAG.length())
1161    {
1162  3 tooltip.append("<br/>");
1163    }
1164    // TODO: limit precision of score to avoid noise from imprecise
1165    // doubles
1166    // (64.7 becomes 64.7+/some tiny value).
1167  5 tooltip.append(" Score: ").append(String.valueOf(aa.score));
1168    }
1169   
1170  12 if (tooltip.length() > HTML_START_TAG.length())
1171    {
1172  10 return tooltip.append(HTML_END_TAG).toString();
1173    }
1174   
1175    /*
1176    * nothing in the tooltip (except "<html>")
1177    */
1178  2 return null;
1179    }
1180   
1181    /**
1182    * Shows the height adjuster image if the mouse moves into the top left
1183    * region, or hides it if the mouse leaves the regio
1184    *
1185    * @param evt
1186    */
 
1187  0 toggle protected void showOrHideAdjuster(MouseEvent evt)
1188    {
1189  0 boolean was = resizePanel;
1190  0 resizePanel = evt.getY() < HEIGHT_ADJUSTER_HEIGHT
1191    && evt.getX() < HEIGHT_ADJUSTER_WIDTH;
1192   
1193  0 if (resizePanel != was)
1194    {
1195  0 setCursor(Cursor
1196  0 .getPredefinedCursor(resizePanel ? Cursor.S_RESIZE_CURSOR
1197    : Cursor.DEFAULT_CURSOR));
1198  0 repaint();
1199    }
1200    }
1201   
 
1202  0 toggle @Override
1203    public void mouseClicked(MouseEvent evt)
1204    {
1205  0 final AlignmentAnnotation[] aa = ap.av.getAlignment()
1206    .getAlignmentAnnotation();
1207  0 if (!evt.isPopupTrigger() && SwingUtilities.isLeftMouseButton(evt))
1208    {
1209  0 if (selectedRow > -1 && selectedRow < aa.length)
1210    {
1211  0 if (aa[selectedRow].groupRef != null)
1212    {
1213  0 if (evt.getClickCount() >= 2)
1214    {
1215    // todo: make the ap scroll to the selection - not necessary, first
1216    // click highlights/scrolls, second selects
1217  0 ap.getSeqPanel().ap.getIdPanel().highlightSearchResults(null);
1218    // process modifiers
1219  0 SequenceGroup sg = ap.av.getSelectionGroup();
1220  0 if (sg == null || sg == aa[selectedRow].groupRef
1221    || !(Platform.isControlDown(evt) || evt.isShiftDown()))
1222    {
1223  0 if (Platform.isControlDown(evt) || evt.isShiftDown())
1224    {
1225    // clone a new selection group from the associated group
1226  0 ap.av.setSelectionGroup(
1227    new SequenceGroup(aa[selectedRow].groupRef));
1228    }
1229    else
1230    {
1231    // set selection to the associated group so it can be edited
1232  0 ap.av.setSelectionGroup(aa[selectedRow].groupRef);
1233    }
1234    }
1235    else
1236    {
1237    // modify current selection with associated group
1238  0 int remainToAdd = aa[selectedRow].groupRef.getSize();
1239  0 for (SequenceI sgs : aa[selectedRow].groupRef.getSequences())
1240    {
1241  0 if (jalview.util.Platform.isControlDown(evt))
1242    {
1243  0 sg.addOrRemove(sgs, --remainToAdd == 0);
1244    }
1245    else
1246    {
1247    // notionally, we should also add intermediate sequences from
1248    // last added sequence ?
1249  0 sg.addSequence(sgs, --remainToAdd == 0);
1250    }
1251    }
1252    }
1253   
1254  0 ap.paintAlignment(false, false);
1255  0 PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
1256  0 ap.av.sendSelection();
1257    }
1258    else
1259    {
1260  0 ap.getSeqPanel().ap.getIdPanel().highlightSearchResults(
1261    aa[selectedRow].groupRef.getSequences(null));
1262    }
1263  0 return;
1264    }
1265  0 else if (aa[selectedRow].sequenceRef != null)
1266    {
1267  0 if (evt.getClickCount() == 1)
1268    {
1269  0 ap.getSeqPanel().ap.getIdPanel()
1270    .highlightSearchResults(Arrays.asList(new SequenceI[]
1271    { aa[selectedRow].sequenceRef }));
1272    }
1273  0 else if (evt.getClickCount() >= 2)
1274    {
1275  0 ap.getSeqPanel().ap.getIdPanel().highlightSearchResults(null);
1276  0 SequenceGroup sg = ap.av.getSelectionGroup();
1277  0 if (sg != null)
1278    {
1279    // we make a copy rather than edit the current selection if no
1280    // modifiers pressed
1281    // see Enhancement JAL-1557
1282  0 if (!(Platform.isControlDown(evt) || evt.isShiftDown()))
1283    {
1284  0 sg = new SequenceGroup(sg);
1285  0 sg.clear();
1286  0 sg.addSequence(aa[selectedRow].sequenceRef, false);
1287    }
1288    else
1289    {
1290  0 if (Platform.isControlDown(evt))
1291    {
1292  0 sg.addOrRemove(aa[selectedRow].sequenceRef, true);
1293    }
1294    else
1295    {
1296    // notionally, we should also add intermediate sequences from
1297    // last added sequence ?
1298  0 sg.addSequence(aa[selectedRow].sequenceRef, true);
1299    }
1300    }
1301    }
1302    else
1303    {
1304  0 sg = new SequenceGroup();
1305  0 sg.setStartRes(0);
1306  0 sg.setEndRes(ap.av.getAlignment().getWidth() - 1);
1307  0 sg.addSequence(aa[selectedRow].sequenceRef, false);
1308    }
1309  0 ap.av.setSelectionGroup(sg);
1310  0 ap.paintAlignment(false, false);
1311  0 PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
1312  0 ap.av.sendSelection();
1313    }
1314   
1315    }
1316    }
1317  0 return;
1318    }
1319    }
1320   
1321    /**
1322    * do a single sequence copy to jalview and the system clipboard
1323    *
1324    * @param sq
1325    * sequence to be copied to clipboard
1326    */
 
1327  0 toggle protected void copy_annotseqtoclipboard(SequenceI sq)
1328    {
1329  0 SequenceI[] seqs = new SequenceI[] { sq };
1330  0 String[] omitHidden = null;
1331  0 SequenceI[] dseqs = new SequenceI[] { sq.getDatasetSequence() };
1332  0 if (dseqs[0] == null)
1333    {
1334  0 dseqs[0] = new Sequence(sq);
1335  0 dseqs[0].setSequence(AlignSeq.extractGaps(Comparison.GapChars,
1336    sq.getSequenceAsString()));
1337   
1338  0 sq.setDatasetSequence(dseqs[0]);
1339    }
1340  0 Alignment ds = new Alignment(dseqs);
1341  0 if (av.hasHiddenColumns())
1342    {
1343  0 Iterator<int[]> it = av.getAlignment().getHiddenColumns()
1344    .getVisContigsIterator(0, sq.getLength() + 1, false);
1345  0 omitHidden = new String[] { sq.getSequenceStringFromIterator(it) };
1346    }
1347   
1348  0 int[] alignmentStartEnd = new int[] { 0, ds.getWidth() - 1 };
1349  0 if (av.hasHiddenColumns())
1350    {
1351  0 alignmentStartEnd = av.getAlignment().getHiddenColumns()
1352    .getVisibleStartAndEndIndex(av.getAlignment().getWidth());
1353    }
1354   
1355  0 String output = new FormatAdapter().formatSequences(FileFormat.Fasta,
1356    seqs, omitHidden, alignmentStartEnd);
1357   
1358  0 Toolkit.getDefaultToolkit().getSystemClipboard()
1359    .setContents(new StringSelection(output), Desktop.getInstance());
1360   
1361  0 HiddenColumns hiddenColumns = null;
1362   
1363  0 if (av.hasHiddenColumns())
1364    {
1365  0 hiddenColumns = new HiddenColumns(
1366    av.getAlignment().getHiddenColumns());
1367    }
1368   
1369    // what is the dataset of a consensus sequence?
1370    // need to flag sequence as special.
1371  0 Desktop.getInstance().jalviewClipboard = new Object[] { seqs, ds,
1372    hiddenColumns };
1373    }
1374   
 
1375  2558 toggle @Override
1376    public void paintComponent(Graphics g)
1377    {
1378   
1379  2558 int width = getWidth();
1380  2558 if (width == 0)
1381    {
1382  0 width = ap.calculateIdWidth().width;
1383    }
1384   
1385  2558 Graphics2D g2 = (Graphics2D) g;
1386  2558 if (av.antiAlias)
1387    {
1388  754 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
1389    RenderingHints.VALUE_ANTIALIAS_ON);
1390    }
1391   
1392  2558 drawComponent(g2, true, width, true);
1393    }
1394   
1395    /**
1396    * Draw the full set of annotation Labels for the alignment at the given
1397    * cursor
1398    *
1399    * @param g
1400    * Graphics2D instance (needed for font scaling)
1401    * @param width
1402    * Width for scaling labels
1403    *
1404    */
 
1405  0 toggle public void drawComponent(Graphics g, int width)
1406    {
1407  0 drawComponent(g, false, width, true);
1408    }
1409   
1410    /**
1411    * Draw the full set of annotation Labels for the alignment at the given
1412    * cursor
1413    *
1414    * @param g
1415    * Graphics2D instance (needed for font scaling)
1416    * @param clip
1417    * - true indicates that only current visible area needs to be
1418    * rendered
1419    * @param width
1420    * Width for scaling labels
1421    */
 
1422  2602 toggle public void drawComponent(Graphics g, boolean clip, int givenWidth,
1423    boolean forGUI)
1424    {
1425  2602 int width = givenWidth;
1426  2602 IdwidthAdjuster iwa = null;
1427  2602 if (ap != null)
1428    {
1429  2589 iwa = ap.idwidthAdjuster;
1430  2589 if (Cache.getDefault(ADJUST_ANNOTATION_LABELS_WIDTH_PREF, true)
1431    || Jalview.isHeadlessMode())
1432    {
1433  2589 Graphics2D g2d = (Graphics2D) g;
1434  2589 Graphics dummy = g2d.create();
1435  2589 int newAnnotationIdWidth = drawLabels(dummy, clip, width, false,
1436    forGUI, null, false);
1437  2589 dummy.dispose();
1438  2589 Dimension d = ap.calculateDefaultAlignmentIdWidth();
1439  2589 int alignmentIdWidth = d.width;
1440  2589 if (iwa != null && !iwa.manuallyAdjusted())
1441    {
1442    // If no manual adjustment to ID column with has been made then adjust
1443    // width match widest of alignment or annotation id widths
1444  2382 boolean allowShrink = Cache.getDefault("ALLOW_SHRINK_ID_WIDTH",
1445    false);
1446  2382 width = Math.max(alignmentIdWidth, newAnnotationIdWidth);
1447  2382 if (clip && width < givenWidth && !allowShrink)
1448    {
1449  7 width = givenWidth;
1450    }
1451    }
1452  207 else if (newAnnotationIdWidth != annotationIdWidth
1453    && newAnnotationIdWidth > givenWidth
1454    && newAnnotationIdWidth > alignmentIdWidth)
1455    {
1456    // otherwise if the annotation id width has become larger than the
1457    // current id width, increase
1458  10 width = newAnnotationIdWidth;
1459  10 annotationIdWidth = newAnnotationIdWidth;
1460    }
1461    // set the width if it's changed
1462  2589 if (width != ap.av.getIdWidth())
1463    {
1464  155 iwa.setWidth(width);
1465    }
1466    }
1467    }
1468    else
1469    {
1470  13 int newAnnotationIdWidth = drawLabels(g, clip, width, false, forGUI,
1471    null, false);
1472  13 width = newAnnotationIdWidth < givenWidth ? givenWidth
1473    : Math.min(newAnnotationIdWidth, givenWidth);
1474    }
1475  2602 drawLabels(g, clip, width, true, forGUI, null, false);
1476    }
1477   
1478    /**
1479    * Render the full set of annotation Labels for the alignment at the given
1480    * cursor. If actuallyDraw is false or g is null then no actual drawing will
1481    * occur, but the widest label width will be returned. If g is null then
1482    * fmetrics must be supplied.
1483    *
1484    * @param g
1485    * Graphics2D instance (used for rendering and font scaling if no
1486    * fmetrics supplied)
1487    * @param clip
1488    * - true indicates that only current visible area needs to be
1489    * rendered
1490    * @param width
1491    * Width for scaling labels
1492    * @param actuallyDraw
1493    * - when false, no graphics are rendered to g0
1494    * @param forGUI
1495    * - when false, GUI relevant marks like indicators for dragging
1496    * annotation panel height are not rendered
1497    * @param fmetrics
1498    * FontMetrics if Graphics object g is null
1499    * @param includeHidden
1500    * - when true returned width includes labels in hidden row width
1501    * calculation
1502    * @return the width of the annotation labels.
1503    */
 
1504  5295 toggle public int drawLabels(Graphics g0, boolean clip, int width,
1505    boolean actuallyDraw, boolean forGUI, FontMetrics fmetrics,
1506    boolean includeHidden)
1507    {
1508  5295 if (clip)
1509    {
1510  5116 clip = Cache.getDefault("MOVE_SEQUENCE_ID_WITH_VISIBLE_ANNOTATIONS",
1511    true);
1512    }
1513  5295 Graphics g = null;
1514    // create a dummy Graphics object if not drawing and one is supplied
1515  5295 if (g0 != null)
1516    {
1517  5212 if (!actuallyDraw)
1518    {
1519  2610 Graphics2D g2d = (Graphics2D) g0;
1520  2610 g = g2d.create();
1521    }
1522    else
1523    {
1524  2602 g = g0;
1525    }
1526    }
1527  5295 int actualWidth = 0;
1528  5295 if (g != null)
1529    {
1530  5212 if (av.getFont().getSize() < 10)
1531    {
1532  0 g.setFont(font);
1533    }
1534    else
1535    {
1536  5212 g.setFont(av.getFont());
1537    }
1538    }
1539   
1540  5295 FontMetrics fm = fmetrics == null ? g.getFontMetrics(g.getFont())
1541    : fmetrics;
1542  5295 if (actuallyDraw)
1543    {
1544  2602 g.setColor(Color.white);
1545  2602 g.fillRect(0, 0, getWidth(), getHeight());
1546   
1547  2602 if (!Cache.getDefault(RESIZE_MARGINS_MARK_PREF, false)
1548    && !av.getWrapAlignment() && forGUI)
1549    {
1550  2558 g.setColor(Color.LIGHT_GRAY);
1551  2558 g.drawLine(0, HEIGHT_ADJUSTER_HEIGHT / 4, HEIGHT_ADJUSTER_WIDTH / 4,
1552    HEIGHT_ADJUSTER_HEIGHT / 4);
1553  2558 g.drawLine(0, 3 * HEIGHT_ADJUSTER_HEIGHT / 4,
1554    HEIGHT_ADJUSTER_WIDTH / 4, 3 * HEIGHT_ADJUSTER_HEIGHT / 4);
1555   
1556    }
1557    }
1558   
1559  5295 if (actuallyDraw)
1560    {
1561  2602 g.translate(0, getScrollOffset());
1562  2602 g.setColor(Color.black);
1563    }
1564   
1565  5295 SequenceGroup selectionGroup = av.getSelectionGroup();
1566  5295 boolean sequencesAreSelected = selectionGroup!=null && selectionGroup.getSize()>0;
1567  5295 boolean annotationsAreSelected = selectionGroup!=null && selectionGroup.hasAnnotationsFromTree();
1568   
1569  5295 SequenceI lastSeqRef = null;
1570  5295 String lastLabel = null;
1571  5295 String lastDescription = null;
1572  5295 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
1573  5295 boolean isShowStructureProvider = av.isShowStructureProvider();
1574  5295 int fontHeight = g != null ? g.getFont().getSize()
1575    : fm.getFont().getSize();
1576  5295 int y = 0;
1577  5295 int x = 0;
1578  5295 int graphExtras = 0;
1579  5295 int offset = 0;
1580  5295 Font baseFont = g != null ? g.getFont() : fm.getFont();
1581  5295 FontMetrics baseMetrics = fm;
1582  5295 int ofontH = fontHeight;
1583  5295 int sOffset = 0;
1584  5295 int visHeight = 0;
1585  5295 int[] visr = (ap != null && ap.getAnnotationPanel() != null)
1586    ? ap.getAnnotationPanel().getVisibleVRange()
1587    : null;
1588  5295 if (clip && visr != null)
1589    {
1590  5116 sOffset = visr[0];
1591  5116 visHeight = visr[1];
1592    }
1593  5295 boolean visible = true, before = false, after = false;
1594  5295 if (aa != null)
1595    {
1596  5295 hasHiddenRows = false;
1597  5295 int olY = 0;
1598  5295 int nexAA = 0;
1599  35676 for (int i = 0; i < aa.length; i++)
1600    {
1601  30381 visible = true;
1602  30381 if (!aa[i].isForDisplay() && !includeHidden)
1603    {
1604  4980 hasHiddenRows = true;
1605  4980 continue;
1606    }
1607  25401 olY = y;
1608    // look ahead to next annotation
1609  25401 for (nexAA = i + 1; nexAA < aa.length
1610    && (!aa[nexAA].isForDisplay() && includeHidden); nexAA++)
1611  0 ;
1612  25401 y += aa[i].height;
1613  25401 if (clip)
1614    {
1615  24718 if (y < sOffset)
1616    {
1617  0 if (!before)
1618    {
1619  0 if (debugRedraw)
1620    {
1621  0 jalview.bin.Console.outPrintln("before vis: " + i);
1622    }
1623  0 before = true;
1624    }
1625    // don't draw what isn't visible
1626  0 continue;
1627    }
1628  24718 if (olY > visHeight)
1629    {
1630   
1631  2016 if (!after)
1632    {
1633  880 if (debugRedraw)
1634    {
1635  0 jalview.bin.Console.outPrintln(
1636    "Scroll offset: " + sOffset + " after vis: " + i);
1637    }
1638  880 after = true;
1639    }
1640    // don't draw what isn't visible
1641  2016 continue;
1642    }
1643    }
1644  23385 if (actuallyDraw && g != null)
1645    {
1646  11526 g.setColor(Color.black);
1647    }
1648  23385 offset = -aa[i].height / 2;
1649   
1650  23385 if (aa[i].hasText)
1651    {
1652  18328 offset += fm.getHeight() / 2;
1653  18328 offset -= fm.getDescent();
1654    }
1655    else
1656    {
1657  5057 offset += fm.getDescent();
1658    }
1659  23385 String label = aa[i].label;
1660  23385 ParseHtmlBodyAndLinks phbDecription = new ParseHtmlBodyAndLinks(
1661    aa[i].description, true, "\n");
1662  23385 String description = phbDecription.getNonHtmlContent();
1663  23385 boolean vertBar = false;
1664  23385 if (lastLabel != null && lastLabel.equals(label) &&
1665    lastDescription != null && lastDescription.equals(description))
1666    {
1667    //JAL-4427
1668  267 label = aa[i].label; // No change in label
1669    }
1670  23118 else if (lastLabel != null && lastLabel.equals(label))
1671    {
1672    //JAL-4427
1673  713 if(!(lastDescription != null && lastDescription.equals(description))
1674    && (aa[i].sequenceRef == lastSeqRef)) {
1675  316 label = description;
1676  316 lastDescription = description;
1677    }
1678    }
1679    else
1680    {
1681  22405 if (nexAA < aa.length && label.equals(aa[nexAA].label)) // &&
1682    // aa[nexY].sequenceRef==aa[i].sequenceRef)
1683    {
1684  161 lastLabel = label;
1685    // next label is the same as this label
1686    //JAL-4427
1687  161 if((lastDescription != null && !lastDescription.equals(description))) {
1688  56 if(aa[nexAA].sequenceRef == aa[i].sequenceRef) {
1689  0 label = description;
1690    }
1691  56 lastDescription = description;
1692    }
1693    }
1694    else
1695    {
1696  22244 lastLabel = label;
1697  22244 lastDescription = description;
1698    }
1699    }
1700  23385 if (aa[i].sequenceRef != null)
1701    {
1702  4931 if (aa[i].sequenceRef != lastSeqRef)
1703    {
1704  2158 label = aa[i].sequenceRef.getName() + " " + label;
1705    // TODO record relationship between sequence and this annotation and
1706    // display it here
1707    }
1708    else
1709    {
1710  2773 vertBar = true;
1711    }
1712    }
1713   
1714  23385 if (isShowStructureProvider && aa[i].hasIcons
1715    && Constants.SECONDARY_STRUCTURE_LABELS.keySet()
1716    .contains(aa[i].label))
1717    {
1718  896 String ssSource = AlignmentAnnotationUtils
1719    .extractSSSourceFromAnnotationDescription(aa[i]);
1720  896 if (ssSource != null && ssSource.length() > 0)
1721  896 label += " (" + ssSource + ")";
1722    }
1723   
1724  23385 int labelWidth = fm.stringWidth(label) + 3;
1725  23385 x = width - labelWidth;
1726   
1727  23385 if (aa[i].graphGroup > -1)
1728    {
1729  84 int groupSize = 0;
1730    // TODO: JAL-1291 revise rendering model so the graphGroup map is
1731    // computed efficiently for all visible labels
1732  2940 for (int gg = 0; gg < aa.length; gg++)
1733    {
1734  2856 if (aa[gg].graphGroup == aa[i].graphGroup)
1735    {
1736  168 groupSize++;
1737    }
1738    }
1739  84 if (groupSize * (fontHeight + 8) < aa[i].height)
1740    {
1741  80 graphExtras = (aa[i].height - (groupSize * (fontHeight + 8)))
1742    / 2;
1743    }
1744    else
1745    {
1746    // scale font to fit
1747  4 float h = aa[i].height / (float) groupSize, s;
1748  4 if (h < 9)
1749    {
1750  4 visible = false;
1751    }
1752    else
1753    {
1754  0 fontHeight = -8 + (int) h;
1755  0 s = ((float) fontHeight) / (float) ofontH;
1756  0 Font f = baseFont
1757    .deriveFont(AffineTransform.getScaleInstance(s, s));
1758  0 Canvas c = new Canvas();
1759  0 fm = c.getFontMetrics(f);
1760  0 if (actuallyDraw && g != null)
1761    {
1762  0 g.setFont(f);
1763    // fm = g.getFontMetrics();
1764  0 graphExtras = (aa[i].height
1765    - (groupSize * (fontHeight + 8))) / 2;
1766    }
1767    }
1768    }
1769  84 if (visible)
1770    {
1771  2800 for (int gg = 0; gg < aa.length; gg++)
1772    {
1773  2720 if (aa[gg].graphGroup == aa[i].graphGroup)
1774    {
1775  160 labelWidth = fm.stringWidth(aa[gg].label) + 3;
1776  160 x = width - labelWidth;
1777  160 if (actuallyDraw && g != null)
1778    {
1779  80 g.drawString(aa[gg].label, x, y - graphExtras);
1780   
1781  80 if (aa[gg]._linecolour != null)
1782    {
1783   
1784  80 g.setColor(aa[gg]._linecolour);
1785  80 g.drawLine(x, y - graphExtras + 3,
1786    x + fm.stringWidth(aa[gg].label),
1787    y - graphExtras + 3);
1788    }
1789   
1790  80 g.setColor(Color.black);
1791    }
1792  160 graphExtras += fontHeight + 8;
1793    }
1794    }
1795    }
1796  84 if (actuallyDraw && g != null)
1797    {
1798  42 g.setFont(baseFont);
1799    }
1800  84 fm = baseMetrics;
1801  84 fontHeight = ofontH;
1802    }
1803    else
1804    {
1805  23301 if (actuallyDraw && g != null)
1806    {
1807  11484 if (vertBar)
1808    {
1809  1370 g.drawLine(width - 3, y + offset - fontHeight, width - 3,
1810    (int) (y - 1.5 * aa[i].height - offset - fontHeight));
1811    // g.drawLine(20, y + offset, x - 20, y + offset);
1812   
1813    }
1814  11484 if(label.contains("Secondary Structure Consensus")) {
1815    //label.replace("Secondary Structure Consensus", "Secondary Structure Consensus" + "\n");
1816    // Split the string into lines using the newline character
1817  0 String[] lines = label.split("(?<=Secondary Structure Consensus)", 2);
1818   
1819    // Set the starting y position
1820  0 int lineHeight = g.getFontMetrics().getHeight();
1821  0 labelWidth = Math.max(fm.stringWidth(lines[0]), fm.stringWidth(lines[1])) + 3;
1822   
1823  0 x = width - labelWidth;
1824  0 for (int k = 0; k < lines.length; k++) {
1825    // Draw each line, offsetting the y position by lineHeight for each line
1826  0 g.drawString(lines[k].trim(), x, y + offset +(k * lineHeight));
1827    }
1828    }
1829    else
1830    {
1831  11484 if (aa[i].sequenceRef != null)
1832    {
1833   
1834  2401 boolean isInSequencePanelSelectionGroup = sequencesAreSelected
1835    && selectionGroup.contains(aa[i].sequenceRef);
1836   
1837  2401 boolean isInTreeSelectionGroup = annotationsAreSelected
1838    && selectionGroup.containsAnnotation(aa[i]);
1839   
1840  2401 if (isInTreeSelectionGroup
1841    || (isInSequencePanelSelectionGroup
1842    && !annotationsAreSelected))
1843    {
1844    // Highlights selection from seq panel or annotation based
1845    // tree
1846  0 g.setColor(Color.lightGray);
1847  0 g.fillRect(0, y + offset - fontHeight, x + labelWidth - 3,
1848    fontHeight + 2);
1849    }
1850  2401 else if (aa[i].getAnnotationGroupColour() != null)
1851    {
1852    // Highlight annotation labels with corresponding group
1853    // colours
1854    // (grouped by secondary structure similarity tree)
1855  264 g.setColor(aa[i].getAnnotationGroupColour());
1856  264 g.fillRect(0, y + offset - fontHeight, x + labelWidth - 3,
1857    fontHeight + 2);
1858    }
1859    }
1860  11484 g.setColor(Color.BLACK);
1861  11484 g.drawString(label, x, y + offset);
1862    }
1863    }
1864    }
1865  23385 lastSeqRef = aa[i].sequenceRef;
1866   
1867  23385 if (labelWidth > actualWidth)
1868    {
1869  6254 actualWidth = labelWidth;
1870    }
1871    }
1872    }
1873   
1874  5295 if (!resizePanel && dragEvent != null && aa != null && selectedRow > -1
1875    && selectedRow < aa.length)
1876    {
1877  0 if (actuallyDraw && g != null)
1878    {
1879  0 g.setColor(Color.lightGray);
1880  0 g.drawString(
1881  0 (aa[selectedRow].sequenceRef == null ? ""
1882    : aa[selectedRow].sequenceRef.getName())
1883    + aa[selectedRow].label,
1884    dragEvent.getX(), dragEvent.getY() - getScrollOffset());
1885    }
1886    }
1887   
1888  5295 if (!av.getWrapAlignment() && ((aa == null) || (aa.length < 1)))
1889    {
1890  0 if (actuallyDraw && g != null)
1891    {
1892  0 g.drawString(MessageManager.getString("label.right_click"), 2, 8);
1893  0 g.drawString(MessageManager.getString("label.to_add_annotation"), 2,
1894    18);
1895    }
1896    }
1897   
1898  5295 return actualWidth;
1899    }
1900   
 
1901  11064 toggle public int getScrollOffset()
1902    {
1903  11064 return scrollOffset;
1904    }
1905   
 
1906  0 toggle @Override
1907    public void mouseEntered(MouseEvent e)
1908    {
1909    }
1910   
 
1911  31 toggle public void drawComponentNotGUI(Graphics idGraphics, int idWidth)
1912    {
1913  31 drawComponent(idGraphics, false, idWidth, false);
1914    }
1915    }