JAL-1557 partial implementation - need to think about how to do SHIFT+double click...
[jalview.git] / src / jalview / gui / AnnotationLabels.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
3  * Copyright (C) 2014 The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.gui;
22
23 import java.util.*;
24 import java.util.regex.Pattern;
25
26 import java.awt.*;
27 import java.awt.datatransfer.*;
28 import java.awt.event.*;
29 import java.awt.geom.AffineTransform;
30 import java.awt.image.*;
31 import javax.swing.*;
32
33 import jalview.datamodel.*;
34 import jalview.io.*;
35 import jalview.util.MessageManager;
36
37 /**
38  * DOCUMENT ME!
39  * 
40  * @author $author$
41  * @version $Revision$
42  */
43 public class AnnotationLabels extends JPanel implements MouseListener,
44         MouseMotionListener, ActionListener
45 {
46   String TOGGLE_LABELSCALE = MessageManager.getString("label.scale_label_to_column");
47
48   String ADDNEW = MessageManager.getString("label.add_new_row");
49
50   String EDITNAME = MessageManager.getString("label.edit_label_description");
51
52   String HIDE = MessageManager.getString("label.hide_row");
53
54   String DELETE = MessageManager.getString("label.delete_row");
55
56   String SHOWALL = MessageManager.getString("label.show_all_hidden_rows");
57
58   String OUTPUT_TEXT = MessageManager.getString("label.export_annotation");
59
60   String COPYCONS_SEQ = MessageManager.getString("label.copy_consensus_sequence");
61
62   boolean resizePanel = false;
63
64   Image image;
65
66   AlignmentPanel ap;
67
68   AlignViewport av;
69
70   boolean resizing = false;
71
72   MouseEvent dragEvent;
73
74   int oldY;
75
76   int selectedRow;
77
78   int scrollOffset = 0;
79
80   Font font = new Font("Arial", Font.PLAIN, 11);
81
82   private boolean hasHiddenRows;
83
84   /**
85    * Creates a new AnnotationLabels object.
86    * 
87    * @param ap
88    *          DOCUMENT ME!
89    */
90   public AnnotationLabels(AlignmentPanel ap)
91   {
92     this.ap = ap;
93     av = ap.av;
94     ToolTipManager.sharedInstance().registerComponent(this);
95
96     java.net.URL url = getClass().getResource("/images/idwidth.gif");
97     Image temp = null;
98
99     if (url != null)
100     {
101       temp = java.awt.Toolkit.getDefaultToolkit().createImage(url);
102     }
103
104     try
105     {
106       MediaTracker mt = new MediaTracker(this);
107       mt.addImage(temp, 0);
108       mt.waitForID(0);
109     } catch (Exception ex)
110     {
111     }
112
113     BufferedImage bi = new BufferedImage(temp.getHeight(this),
114             temp.getWidth(this), BufferedImage.TYPE_INT_RGB);
115     Graphics2D g = (Graphics2D) bi.getGraphics();
116     g.rotate(Math.toRadians(90));
117     g.drawImage(temp, 0, -bi.getWidth(this), this);
118     image = (Image) bi;
119
120     addMouseListener(this);
121     addMouseMotionListener(this);
122     addMouseWheelListener(ap.annotationPanel);
123   }
124
125   public AnnotationLabels(AlignViewport av)
126   {
127     this.av = av;
128   }
129
130   /**
131    * DOCUMENT ME!
132    * 
133    * @param y
134    *          DOCUMENT ME!
135    */
136   public void setScrollOffset(int y)
137   {
138     scrollOffset = y;
139     repaint();
140   }
141
142   /**
143    * sets selectedRow to -2 if no annotation preset, -1 if no visible row is at
144    * y
145    * 
146    * @param y
147    *          coordinate position to search for a row
148    */
149   void getSelectedRow(int y)
150   {
151     int height = 0;
152     AlignmentAnnotation[] aa = ap.av.getAlignment()
153             .getAlignmentAnnotation();
154     selectedRow = -2;
155     if (aa != null)
156     {
157       for (int i = 0; i < aa.length; i++)
158       {
159         selectedRow = -1;
160         if (!aa[i].visible)
161         {
162           continue;
163         }
164
165         height += aa[i].height;
166
167         if (y < height)
168         {
169           selectedRow = i;
170
171           break;
172         }
173       }
174     }
175   }
176
177   /**
178    * DOCUMENT ME!
179    * 
180    * @param evt
181    *          DOCUMENT ME!
182    */
183   public void actionPerformed(ActionEvent evt)
184   {
185     AlignmentAnnotation[] aa = ap.av.getAlignment()
186             .getAlignmentAnnotation();
187
188     if (evt.getActionCommand().equals(ADDNEW))
189     {
190       AlignmentAnnotation newAnnotation = new AlignmentAnnotation(null,
191               null, new Annotation[ap.av.getAlignment().getWidth()]);
192
193       if (!editLabelDescription(newAnnotation))
194       {
195         return;
196       }
197
198       ap.av.getAlignment().addAnnotation(newAnnotation);
199       ap.av.getAlignment().setAnnotationIndex(newAnnotation, 0);
200     }
201     else if (evt.getActionCommand().equals(EDITNAME))
202     {
203       editLabelDescription(aa[selectedRow]);
204       repaint();
205     }
206     else if (evt.getActionCommand().equals(HIDE))
207     {
208       aa[selectedRow].visible = false;
209     }
210     else if (evt.getActionCommand().equals(DELETE))
211     {
212       ap.av.getAlignment().deleteAnnotation(aa[selectedRow]);
213     }
214     else if (evt.getActionCommand().equals(SHOWALL))
215     {
216       for (int i = 0; i < aa.length; i++)
217       {
218         if (!aa[i].visible && aa[i].annotations != null)
219         {
220           aa[i].visible = true;
221         }
222       }
223     }
224     else if (evt.getActionCommand().equals(OUTPUT_TEXT))
225     {
226       new AnnotationExporter().exportAnnotations(ap,
227               new AlignmentAnnotation[]
228               { aa[selectedRow] }, null, null);
229     }
230     else if (evt.getActionCommand().equals(COPYCONS_SEQ))
231     {
232       SequenceI cons = null;
233       if (aa[selectedRow].groupRef != null)
234       {
235         cons = aa[selectedRow].groupRef.getConsensusSeq();
236       }
237       else
238       {
239         cons = av.getConsensusSeq();
240       }
241       if (cons != null)
242       {
243         copy_annotseqtoclipboard(cons);
244       }
245
246     }
247     else if (evt.getActionCommand().equals(TOGGLE_LABELSCALE))
248     {
249       aa[selectedRow].scaleColLabel = !aa[selectedRow].scaleColLabel;
250     }
251
252     ap.validateAnnotationDimensions(false);
253     ap.addNotify();
254     ap.repaint();
255     // validate();
256     // ap.paintAlignment(true);
257   }
258
259   /**
260    * DOCUMENT ME!
261    * 
262    * @param e
263    *          DOCUMENT ME!
264    */
265   boolean editLabelDescription(AlignmentAnnotation annotation)
266   {
267     EditNameDialog dialog = new EditNameDialog(annotation.label,
268             annotation.description, "       Annotation Name ",
269             "Annotation Description ", "Edit Annotation Name/Description",
270             ap.alignFrame);
271
272     if (!dialog.accept)
273     {
274       return false;
275     }
276
277     annotation.label = dialog.getName();
278
279     String text = dialog.getDescription();
280     if (text != null && text.length() == 0)
281     {
282       text = null;
283     }
284     annotation.description = text;
285
286     return true;
287   }
288
289   /**
290    * DOCUMENT ME!
291    * 
292    * @param evt
293    *          DOCUMENT ME!
294    */
295   public void mousePressed(MouseEvent evt)
296   {
297     getSelectedRow(evt.getY() - scrollOffset);
298     oldY = evt.getY();
299   }
300
301   /**
302    * DOCUMENT ME!
303    * 
304    * @param evt
305    *          DOCUMENT ME!
306    */
307   public void mouseReleased(MouseEvent evt)
308   {
309     int start = selectedRow;
310     getSelectedRow(evt.getY() - scrollOffset);
311     int end = selectedRow;
312
313     if (start != end)
314     {
315       // Swap these annotations
316       AlignmentAnnotation startAA = ap.av.getAlignment()
317               .getAlignmentAnnotation()[start];
318       if (end == -1)
319       {
320         end = ap.av.getAlignment().getAlignmentAnnotation().length - 1;
321       }
322       AlignmentAnnotation endAA = ap.av.getAlignment()
323               .getAlignmentAnnotation()[end];
324
325       ap.av.getAlignment().getAlignmentAnnotation()[end] = startAA;
326       ap.av.getAlignment().getAlignmentAnnotation()[start] = endAA;
327     }
328
329     resizePanel = false;
330     dragEvent = null;
331     repaint();
332     ap.annotationPanel.repaint();
333   }
334
335   /**
336    * DOCUMENT ME!
337    * 
338    * @param evt
339    *          DOCUMENT ME!
340    */
341   public void mouseEntered(MouseEvent evt)
342   {
343     if (evt.getY() < 10)
344     {
345       resizePanel = true;
346       repaint();
347     }
348   }
349
350   /**
351    * DOCUMENT ME!
352    * 
353    * @param evt
354    *          DOCUMENT ME!
355    */
356   public void mouseExited(MouseEvent evt)
357   {
358     if (dragEvent == null)
359     {
360       resizePanel = false;
361       repaint();
362     }
363   }
364
365   /**
366    * DOCUMENT ME!
367    * 
368    * @param evt
369    *          DOCUMENT ME!
370    */
371   public void mouseDragged(MouseEvent evt)
372   {
373     dragEvent = evt;
374
375     if (resizePanel)
376     {
377       Dimension d = ap.annotationScroller.getPreferredSize();
378       int dif = evt.getY() - oldY;
379
380       dif /= ap.av.charHeight;
381       dif *= ap.av.charHeight;
382
383       if ((d.height - dif) > 20)
384       {
385         ap.annotationScroller.setPreferredSize(new Dimension(d.width,
386                 d.height - dif));
387         d = ap.annotationSpaceFillerHolder.getPreferredSize();
388         ap.annotationSpaceFillerHolder.setPreferredSize(new Dimension(
389                 d.width, d.height - dif));
390         ap.paintAlignment(true);
391       }
392
393       ap.addNotify();
394     }
395     else
396     {
397       repaint();
398     }
399   }
400
401   /**
402    * DOCUMENT ME!
403    * 
404    * @param evt
405    *          DOCUMENT ME!
406    */
407   public void mouseMoved(MouseEvent evt)
408   {
409     resizePanel = evt.getY() < 10;
410
411     getSelectedRow(evt.getY() - scrollOffset);
412
413     if (selectedRow > -1
414             && ap.av.getAlignment().getAlignmentAnnotation().length > selectedRow)
415     {
416       AlignmentAnnotation aa = ap.av.getAlignment()
417               .getAlignmentAnnotation()[selectedRow];
418
419       StringBuffer desc = new StringBuffer();
420       if (aa.description != null
421               && !aa.description.equals("New description"))
422       {
423         // TODO: we could refactor and merge this code with the code in
424         // jalview.gui.SeqPanel.mouseMoved(..) that formats sequence feature
425         // tooltips
426         desc.append(aa.getDescription(true).trim());
427         // check to see if the description is an html fragment.
428         if (desc.length() < 6
429                 || (desc.substring(0, 6).toLowerCase().indexOf("<html>") < 0))
430         {
431           // clean the description ready for embedding in html
432           desc = new StringBuffer(Pattern.compile("<").matcher(desc)
433                   .replaceAll("&lt;"));
434           desc.insert(0, "<html>");
435         }
436         else
437         {
438           // remove terminating html if any
439           int i = desc.substring(desc.length() - 7).toLowerCase()
440                   .lastIndexOf("</html>");
441           if (i > -1)
442           {
443             desc.setLength(desc.length() - 7 + i);
444           }
445         }
446         if (aa.hasScore())
447         {
448           desc.append("<br/>");
449         }
450
451       }
452       else
453       {
454         // begin the tooltip's html fragment
455         desc.append("<html>");
456       }
457       if (aa.hasScore())
458       {
459         // TODO: limit precision of score to avoid noise from imprecise doubles
460         // (64.7 becomes 64.7+/some tiny value).
461         desc.append(" Score: " + aa.score);
462       }
463
464       if (desc.length() > 6)
465       {
466         desc.append("</html>");
467         this.setToolTipText(desc.toString());
468       }
469       else
470         this.setToolTipText(null);
471     }
472
473   }
474
475   /**
476    * DOCUMENT ME!
477    * 
478    * @param evt
479    *          DOCUMENT ME!
480    */
481   public void mouseClicked(MouseEvent evt)
482   {
483     AlignmentAnnotation[] aa = ap.av.getAlignment()
484             .getAlignmentAnnotation();
485     if (SwingUtilities.isLeftMouseButton(evt))
486     {
487       if (selectedRow > -1 && selectedRow < aa.length)
488       {
489         if (aa[selectedRow].groupRef != null)
490         {
491           if (evt.getClickCount() >= 2)
492           {
493             // todo: make the ap scroll to the selection - not necessary, first
494             // click highlights/scrolls, second selects
495             ap.seqPanel.ap.idPanel.highlightSearchResults(null);
496             ap.av.setSelectionGroup(// new SequenceGroup(
497             aa[selectedRow].groupRef); // );
498             ap.paintAlignment(false);
499             PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
500             ap.av.sendSelection();
501           }
502           else
503           {
504             ap.seqPanel.ap.idPanel
505                     .highlightSearchResults(aa[selectedRow].groupRef
506                             .getSequences(null));
507           }
508           return;
509         }
510         else if (aa[selectedRow].sequenceRef != null)
511         {
512           if (evt.getClickCount() == 1)
513           {
514             ap.seqPanel.ap.idPanel.highlightSearchResults(Arrays
515                     .asList(new SequenceI[]
516                     { aa[selectedRow].sequenceRef }));
517           }
518           else if (evt.getClickCount() >= 2)
519           {
520             ap.seqPanel.ap.idPanel.highlightSearchResults(null);
521             SequenceGroup sg = ap.av.getSelectionGroup();
522             if (sg!=null)
523             {
524               // we make a copy rather than edit the current selection if no modifiers pressed
525               // see Enhancement JAL-1557
526               if (!(evt.isControlDown() || evt.isShiftDown()))
527               {
528                 sg = new SequenceGroup(sg);
529                 sg.clear();
530                 sg.addSequence(aa[selectedRow].sequenceRef, false);
531               } else {
532                 if (evt.isControlDown())
533                 {
534                   sg.addOrRemove(aa[selectedRow].sequenceRef, true);
535                 } else {
536                   // notionally, we should also add intermediate sequences from last added sequence ?
537                   sg.addSequence(aa[selectedRow].sequenceRef, true);
538                 }
539               }
540             } else {
541               sg = new SequenceGroup();
542               sg.setStartRes(0);
543               sg.setEndRes(ap.av.getAlignment().getWidth()-1);
544               sg.addSequence(aa[selectedRow].sequenceRef, false);
545             }
546             ap.av.setSelectionGroup(sg);
547             ap.av.sendSelection();
548             ap.paintAlignment(false);
549             PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
550           }
551
552         }
553       }
554     }
555     if (!SwingUtilities.isRightMouseButton(evt))
556     {
557       return;
558     }
559
560     JPopupMenu pop = new JPopupMenu(
561             MessageManager.getString("label.annotations"));
562     JMenuItem item = new JMenuItem(ADDNEW);
563     item.addActionListener(this);
564     pop.add(item);
565     if (selectedRow < 0)
566     {
567       if (hasHiddenRows)
568       { // let the user make everything visible again
569         item = new JMenuItem(SHOWALL);
570         item.addActionListener(this);
571         pop.add(item);
572       }
573       pop.show(this, evt.getX(), evt.getY());
574       return;
575     }
576     item = new JMenuItem(EDITNAME);
577     item.addActionListener(this);
578     pop.add(item);
579     item = new JMenuItem(HIDE);
580     item.addActionListener(this);
581     pop.add(item);
582     item = new JMenuItem(DELETE);
583     item.addActionListener(this);
584     pop.add(item);
585     if (hasHiddenRows)
586     {
587       item = new JMenuItem(SHOWALL);
588       item.addActionListener(this);
589       pop.add(item);
590     }
591     item = new JMenuItem(OUTPUT_TEXT);
592     item.addActionListener(this);
593     pop.add(item);
594     // TODO: annotation object should be typed for autocalculated/derived
595     // property methods
596     if (selectedRow < aa.length)
597     {
598       if (!aa[selectedRow].autoCalculated)
599       {
600         if (aa[selectedRow].graph == AlignmentAnnotation.NO_GRAPH)
601         {
602           // display formatting settings for this row.
603           pop.addSeparator();
604           // av and sequencegroup need to implement same interface for
605           item = new JCheckBoxMenuItem(TOGGLE_LABELSCALE,
606                   aa[selectedRow].scaleColLabel);
607           item.addActionListener(this);
608           pop.add(item);
609         }
610       }
611       else if (aa[selectedRow].label.indexOf("Consensus") > -1)
612       {
613         pop.addSeparator();
614         // av and sequencegroup need to implement same interface for
615         final JCheckBoxMenuItem cbmi = new JCheckBoxMenuItem(
616                 "Ignore Gaps In Consensus",
617                 (aa[selectedRow].groupRef != null) ? aa[selectedRow].groupRef
618                         .getIgnoreGapsConsensus() : ap.av
619                         .getIgnoreGapsConsensus());
620         final AlignmentAnnotation aaa = aa[selectedRow];
621         cbmi.addActionListener(new ActionListener()
622         {
623           public void actionPerformed(ActionEvent e)
624           {
625             if (aaa.groupRef != null)
626             {
627               // TODO: pass on reference to ap so the view can be updated.
628               aaa.groupRef.setIgnoreGapsConsensus(cbmi.getState());
629               ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
630             }
631             else
632             {
633               ap.av.setIgnoreGapsConsensus(cbmi.getState(), ap);
634             }
635           }
636         });
637         pop.add(cbmi);
638         // av and sequencegroup need to implement same interface for
639         if (aaa.groupRef != null)
640         {
641           final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
642                   "Show Group Histogram",
643                   aa[selectedRow].groupRef.isShowConsensusHistogram());
644           chist.addActionListener(new ActionListener()
645           {
646             public void actionPerformed(ActionEvent e)
647             {
648               // TODO: pass on reference
649               // to ap
650               // so the
651               // view
652               // can be
653               // updated.
654               aaa.groupRef.setShowConsensusHistogram(chist.getState());
655               ap.repaint();
656               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
657             }
658           });
659           pop.add(chist);
660           final JCheckBoxMenuItem cprofl = new JCheckBoxMenuItem(
661                   "Show Group Logo",
662                   aa[selectedRow].groupRef.isShowSequenceLogo());
663           cprofl.addActionListener(new ActionListener()
664           {
665             public void actionPerformed(ActionEvent e)
666             {
667               // TODO: pass on reference
668               // to ap
669               // so the
670               // view
671               // can be
672               // updated.
673               aaa.groupRef.setshowSequenceLogo(cprofl.getState());
674               ap.repaint();
675               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
676             }
677           });
678           pop.add(cprofl);
679           final JCheckBoxMenuItem cproflnorm = new JCheckBoxMenuItem(
680                   "Normalise Group Logo",
681                   aa[selectedRow].groupRef.isNormaliseSequenceLogo());
682           cproflnorm.addActionListener(new ActionListener()
683           {
684             public void actionPerformed(ActionEvent e)
685             {
686
687               // TODO: pass on reference
688               // to ap
689               // so the
690               // view
691               // can be
692               // updated.
693               aaa.groupRef.setNormaliseSequenceLogo(cproflnorm.getState());
694               // automatically enable logo display if we're clicked
695               aaa.groupRef.setshowSequenceLogo(true);
696               ap.repaint();
697               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
698             }
699           });
700           pop.add(cproflnorm);
701         }
702         else
703         {
704           final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
705                   "Show Histogram", av.isShowConsensusHistogram());
706           chist.addActionListener(new ActionListener()
707           {
708             public void actionPerformed(ActionEvent e)
709             {
710               // TODO: pass on reference
711               // to ap
712               // so the
713               // view
714               // can be
715               // updated.
716               av.setShowConsensusHistogram(chist.getState());
717               ap.alignFrame.setMenusForViewport();
718               ap.repaint();
719               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
720             }
721           });
722           pop.add(chist);
723           final JCheckBoxMenuItem cprof = new JCheckBoxMenuItem(
724                   "Show Logo", av.isShowSequenceLogo());
725           cprof.addActionListener(new ActionListener()
726           {
727             public void actionPerformed(ActionEvent e)
728             {
729               // TODO: pass on reference
730               // to ap
731               // so the
732               // view
733               // can be
734               // updated.
735               av.setShowSequenceLogo(cprof.getState());
736               ap.alignFrame.setMenusForViewport();
737               ap.repaint();
738               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
739             }
740           });
741           pop.add(cprof);
742           final JCheckBoxMenuItem cprofnorm = new JCheckBoxMenuItem(
743                   "Normalise Logo", av.isNormaliseSequenceLogo());
744           cprofnorm.addActionListener(new ActionListener()
745           {
746             public void actionPerformed(ActionEvent e)
747             {
748               // TODO: pass on reference
749               // to ap
750               // so the
751               // view
752               // can be
753               // updated.
754               av.setShowSequenceLogo(true);
755               av.setNormaliseSequenceLogo(cprofnorm.getState());
756               ap.alignFrame.setMenusForViewport();
757               ap.repaint();
758               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
759             }
760           });
761           pop.add(cprofnorm);
762         }
763         final JMenuItem consclipbrd = new JMenuItem(COPYCONS_SEQ);
764         consclipbrd.addActionListener(this);
765         pop.add(consclipbrd);
766       }
767     }
768     pop.show(this, evt.getX(), evt.getY());
769   }
770
771   /**
772    * do a single sequence copy to jalview and the system clipboard
773    * 
774    * @param sq
775    *          sequence to be copied to clipboard
776    */
777   protected void copy_annotseqtoclipboard(SequenceI sq)
778   {
779     SequenceI[] seqs = new SequenceI[]
780     { sq };
781     String[] omitHidden = null;
782     SequenceI[] dseqs = new SequenceI[]
783     { sq.getDatasetSequence() };
784     if (dseqs[0] == null)
785     {
786       dseqs[0] = new Sequence(sq);
787       dseqs[0].setSequence(jalview.analysis.AlignSeq.extractGaps(
788               jalview.util.Comparison.GapChars, sq.getSequenceAsString()));
789
790       sq.setDatasetSequence(dseqs[0]);
791     }
792     Alignment ds = new Alignment(dseqs);
793     if (av.hasHiddenColumns())
794     {
795       omitHidden = av.getColumnSelection().getVisibleSequenceStrings(0,
796               sq.getLength(), seqs);
797     }
798
799     String output = new FormatAdapter().formatSequences("Fasta", seqs,
800             omitHidden);
801
802     Toolkit.getDefaultToolkit().getSystemClipboard()
803             .setContents(new StringSelection(output), Desktop.instance);
804
805     Vector hiddenColumns = null;
806     if (av.hasHiddenColumns())
807     {
808       hiddenColumns = new Vector();
809       for (int i = 0; i < av.getColumnSelection().getHiddenColumns().size(); i++)
810       {
811         int[] region = (int[]) av.getColumnSelection().getHiddenColumns()
812                 .elementAt(i);
813
814         hiddenColumns.addElement(new int[]
815         { region[0], region[1] });
816       }
817     }
818
819     Desktop.jalviewClipboard = new Object[]
820     { seqs, ds, // what is the dataset of a consensus sequence ? need to flag
821         // sequence as special.
822         hiddenColumns };
823   }
824
825   /**
826    * DOCUMENT ME!
827    * 
828    * @param g1
829    *          DOCUMENT ME!
830    */
831   public void paintComponent(Graphics g)
832   {
833
834     int width = getWidth();
835     if (width == 0)
836     {
837       width = ap.calculateIdWidth().width + 4;
838     }
839
840     Graphics2D g2 = (Graphics2D) g;
841     if (av.antiAlias)
842     {
843       g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
844               RenderingHints.VALUE_ANTIALIAS_ON);
845     }
846
847     drawComponent(g2, true, width);
848
849   }
850
851   /**
852    * Draw the full set of annotation Labels for the alignment at the given
853    * cursor
854    * 
855    * @param g
856    *          Graphics2D instance (needed for font scaling)
857    * @param width
858    *          Width for scaling labels
859    * 
860    */
861   public void drawComponent(Graphics g, int width)
862   {
863     drawComponent(g, false, width);
864   }
865
866   private final boolean debugRedraw = false;
867
868   /**
869    * Draw the full set of annotation Labels for the alignment at the given
870    * cursor
871    * 
872    * @param g
873    *          Graphics2D instance (needed for font scaling)
874    * @param clip
875    *          - true indicates that only current visible area needs to be
876    *          rendered
877    * @param width
878    *          Width for scaling labels
879    */
880   public void drawComponent(Graphics g, boolean clip, int width)
881   {
882     if (av.getFont().getSize() < 10)
883     {
884       g.setFont(font);
885     }
886     else
887     {
888       g.setFont(av.getFont());
889     }
890
891     FontMetrics fm = g.getFontMetrics(g.getFont());
892     g.setColor(Color.white);
893     g.fillRect(0, 0, getWidth(), getHeight());
894
895     g.translate(0, scrollOffset);
896     g.setColor(Color.black);
897
898     AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
899     int fontHeight = g.getFont().getSize();
900     int y = 0;
901     int x = 0;
902     int graphExtras = 0;
903     int offset = 0;
904     Font baseFont = g.getFont();
905     FontMetrics baseMetrics = fm;
906     int ofontH = fontHeight;
907     int sOffset = 0;
908     int visHeight = 0;
909     int[] visr = (ap != null && ap.annotationPanel != null) ? ap.annotationPanel
910             .getVisibleVRange() : null;
911     if (clip && visr != null)
912     {
913       sOffset = visr[0];
914       visHeight = visr[1];
915     }
916     boolean visible = true, before = false, after = false;
917     if (aa != null)
918     {
919       hasHiddenRows = false;
920       int olY = 0;
921       for (int i = 0; i < aa.length; i++)
922       {
923         visible = true;
924         if (!aa[i].visible)
925         {
926           hasHiddenRows = true;
927           continue;
928         }
929         olY = y;
930         y += aa[i].height;
931         if (clip)
932         {
933           if (y < sOffset)
934           {
935             if (!before)
936             {
937               if (debugRedraw)
938               {
939                 System.out.println("before vis: " + i);
940               }
941               before = true;
942             }
943             // don't draw what isn't visible
944             continue;
945           }
946           if (olY > visHeight)
947           {
948
949             if (!after)
950             {
951               if (debugRedraw)
952               {
953                 System.out.println("Scroll offset: " + sOffset
954                         + " after vis: " + i);
955               }
956               after = true;
957             }
958             // don't draw what isn't visible
959             continue;
960           }
961         }
962         g.setColor(Color.black);
963
964         offset = -aa[i].height / 2;
965
966         if (aa[i].hasText)
967         {
968           offset += fm.getHeight() / 2;
969           offset -= fm.getDescent();
970         }
971         else
972           offset += fm.getDescent();
973
974         x = width - fm.stringWidth(aa[i].label) - 3;
975
976         if (aa[i].graphGroup > -1)
977         {
978           int groupSize = 0;
979           // TODO: JAL-1291 revise rendering model so the graphGroup map is
980           // computed efficiently for all visible labels
981           for (int gg = 0; gg < aa.length; gg++)
982           {
983             if (aa[gg].graphGroup == aa[i].graphGroup)
984             {
985               groupSize++;
986             }
987           }
988           if (groupSize * (fontHeight + 8) < aa[i].height)
989           {
990             graphExtras = (aa[i].height - (groupSize * (fontHeight + 8))) / 2;
991           }
992           else
993           {
994             // scale font to fit
995             float h = aa[i].height / (float) groupSize, s;
996             if (h < 9)
997             {
998               visible = false;
999             }
1000             else
1001             {
1002               fontHeight = -8 + (int) h;
1003               s = ((float) fontHeight) / (float) ofontH;
1004               Font f = baseFont.deriveFont(AffineTransform
1005                       .getScaleInstance(s, s));
1006               g.setFont(f);
1007               fm = g.getFontMetrics();
1008               graphExtras = (aa[i].height - (groupSize * (fontHeight + 8))) / 2;
1009             }
1010           }
1011           if (visible)
1012           {
1013             for (int gg = 0; gg < aa.length; gg++)
1014             {
1015               if (aa[gg].graphGroup == aa[i].graphGroup)
1016               {
1017                 x = width - fm.stringWidth(aa[gg].label) - 3;
1018                 g.drawString(aa[gg].label, x, y - graphExtras);
1019
1020                 if (aa[gg]._linecolour != null)
1021                 {
1022
1023                   g.setColor(aa[gg]._linecolour);
1024                   g.drawLine(x, y - graphExtras + 3,
1025                           x + fm.stringWidth(aa[gg].label), y - graphExtras
1026                                   + 3);
1027                 }
1028
1029                 g.setColor(Color.black);
1030                 graphExtras += fontHeight + 8;
1031               }
1032             }
1033           }
1034           g.setFont(baseFont);
1035           fm = baseMetrics;
1036           fontHeight = ofontH;
1037         }
1038         else
1039         {
1040           g.drawString(aa[i].label, x, y + offset);
1041         }
1042       }
1043     }
1044
1045     if (resizePanel)
1046     {
1047       g.drawImage(image, 2, 0 - scrollOffset, this);
1048     }
1049     else if (dragEvent != null && aa != null)
1050     {
1051       g.setColor(Color.lightGray);
1052       g.drawString(aa[selectedRow].label, dragEvent.getX(),
1053               dragEvent.getY() - scrollOffset);
1054     }
1055
1056     if (!av.wrapAlignment && ((aa == null) || (aa.length < 1)))
1057     {
1058       g.drawString(MessageManager.getString("label.right_click"), 2, 8);
1059       g.drawString(MessageManager.getString("label.to_add_annotation"), 2,
1060               18);
1061     }
1062   }
1063 }