Clover icon

Coverage Report

  1. Project Clover database Mon Nov 18 2024 09:38:20 GMT
  2. Package jalview.gui

File AnnotationLabels.java

 

Coverage histogram

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

Code metrics

294
569
42
1
1,693
1,297
238
0.42
13.55
42
5.67

Classes

Class Line # Actions
AnnotationLabels 81 569 238
0.31933731.9%
 

Contributing tests

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