Clover icon

Coverage Report

  1. Project Clover database Wed Nov 12 2025 13:01:44 GMT
  2. Package jalview.gui

File AnnotationLabels.java

 

Coverage histogram

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

Code metrics

304
591
43
1
1,754
1,341
246
0.42
13.74
43
5.72

Classes

Class Line # Actions
AnnotationLabels 82 591 246
0.3571428735.7%
 

Contributing tests

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