corrected subtree colour dialogbox title
[jalview.git] / src / jalview / gui / TreeCanvas.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer
3  * Copyright (C) 2006 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
18  */
19 package jalview.gui;
20
21 import jalview.analysis.*;
22
23 import jalview.datamodel.*;
24
25 import jalview.schemes.*;
26
27 import jalview.util.*;
28
29 import java.awt.*;
30 import java.awt.event.*;
31 import java.awt.print.*;
32
33 import java.util.*;
34
35 import javax.swing.*;
36
37
38 /**
39  * DOCUMENT ME!
40  *
41  * @author $author$
42  * @version $Revision$
43  */
44 public class TreeCanvas extends JPanel implements MouseListener, Runnable,
45     Printable, MouseMotionListener
46 {
47     /** DOCUMENT ME!! */
48     public static final String PLACEHOLDER = " * ";
49     NJTree tree;
50     JScrollPane scrollPane;
51     AlignViewport av;
52     AlignmentPanel ap;
53     Font font;
54     FontMetrics fm;
55     boolean fitToWindow = true;
56     boolean showDistances = false;
57     boolean showBootstrap = false;
58     boolean markPlaceholders = false;
59     int offx = 20;
60     int offy;
61     float threshold;
62     String longestName;
63     int labelLength = -1;
64
65     Hashtable nameHash = new Hashtable();
66     Hashtable nodeHash = new Hashtable();
67     SequenceNode highlightNode;
68
69     /**
70      * Creates a new TreeCanvas object.
71      *
72      * @param av DOCUMENT ME!
73      * @param tree DOCUMENT ME!
74      * @param scroller DOCUMENT ME!
75      * @param label DOCUMENT ME!
76      */
77     public TreeCanvas(AlignmentPanel ap, JScrollPane scroller)
78     {
79         this.av = ap.av;
80         this.ap = ap;
81         font = av.getFont();
82         scrollPane = scroller;
83         addMouseListener(this);
84         addMouseMotionListener(this);
85         PaintRefresher.Register(this, ap.av.getSequenceSetId());
86         ToolTipManager.sharedInstance().registerComponent(this);
87     }
88
89
90     /**
91      * DOCUMENT ME!
92      *
93      * @param sequence DOCUMENT ME!
94      */
95     public void treeSelectionChanged(SequenceI sequence)
96     {
97         SequenceGroup selected = av.getSelectionGroup();
98
99         if (selected == null)
100         {
101             selected = new SequenceGroup();
102             av.setSelectionGroup(selected);
103         }
104
105         selected.setEndRes(av.alignment.getWidth()-1);
106         selected.addOrRemove(sequence, true);
107     }
108
109     /**
110      * DOCUMENT ME!
111      *
112      * @param tree DOCUMENT ME!
113      */
114     public void setTree(NJTree tree)
115     {
116         this.tree = tree;
117         tree.findHeight(tree.getTopNode());
118
119         // Now have to calculate longest name based on the leaves
120         Vector leaves = tree.findLeaves(tree.getTopNode(), new Vector());
121         boolean has_placeholders = false;
122         longestName = "";
123
124         for (int i = 0; i < leaves.size(); i++)
125         {
126           SequenceNode lf = (SequenceNode) leaves.elementAt(i);
127
128           if (lf.isPlaceholder())
129           {
130             has_placeholders = true;
131           }
132
133           if (longestName.length() < ( (Sequence) lf.element()).getName()
134               .length())
135           {
136             longestName = TreeCanvas.PLACEHOLDER +
137                 ( (Sequence) lf.element()).getName();
138           }
139         }
140
141         setMarkPlaceholders(has_placeholders);
142     }
143
144     /**
145      * DOCUMENT ME!
146      *
147      * @param g DOCUMENT ME!
148      * @param node DOCUMENT ME!
149      * @param chunk DOCUMENT ME!
150      * @param scale DOCUMENT ME!
151      * @param width DOCUMENT ME!
152      * @param offx DOCUMENT ME!
153      * @param offy DOCUMENT ME!
154      */
155     public void drawNode(Graphics g, SequenceNode node, float chunk,
156         float scale, int width, int offx, int offy)
157     {
158         if (node == null)
159         {
160             return;
161         }
162
163         if ((node.left() == null) && (node.right() == null))
164         {
165             // Drawing leaf node
166             float height = node.height;
167             float dist = node.dist;
168
169             int xstart = (int) ((height - dist) * scale) + offx;
170             int xend = (int) (height * scale) + offx;
171
172             int ypos = (int) (node.ycount * chunk) + offy;
173
174             if (node.element() instanceof SequenceI)
175             {
176               SequenceI seq = (SequenceI)((SequenceNode) node).element();
177
178                 if (av.getSequenceColour(seq) == Color.white)
179                 {
180                     g.setColor(Color.black);
181                 }
182                 else
183                 {
184                     g.setColor(av.getSequenceColour(seq).darker());
185                 }
186             }
187             else
188             {
189                 g.setColor(Color.black);
190             }
191
192             // Draw horizontal line
193             g.drawLine(xstart, ypos, xend, ypos);
194
195             String nodeLabel = "";
196
197             if (showDistances && (node.dist > 0))
198             {
199                 nodeLabel = new Format("%-.2f").form(node.dist);
200             }
201
202             if (showBootstrap)
203             {
204                 if (showDistances)
205                 {
206                     nodeLabel = nodeLabel + " : ";
207                 }
208
209                 nodeLabel = nodeLabel + String.valueOf(node.getBootstrap());
210             }
211
212             if (!nodeLabel.equals(""))
213             {
214                 g.drawString(nodeLabel, xstart+2, ypos - 2);
215             }
216
217             String name = (markPlaceholders && node.isPlaceholder())
218                 ? (PLACEHOLDER + node.getName()) : node.getName();
219
220             int charWidth = fm.stringWidth(name) + 3;
221             int charHeight = font.getSize();
222
223             Rectangle rect = new Rectangle(xend+10, ypos-charHeight/2,
224                     charWidth, charHeight);
225
226             nameHash.put((SequenceI) node.element(), rect);
227
228             // Colour selected leaves differently
229             SequenceGroup selected = av.getSelectionGroup();
230
231             if ((selected != null) &&
232                     selected.getSequences(false).contains((SequenceI) node.element()))
233             {
234                 g.setColor(Color.gray);
235
236                 g.fillRect(xend + 10, ypos-charHeight/2, charWidth,
237                     charHeight);
238                 g.setColor(Color.white);
239             }
240
241             g.drawString(name, xend + 10, ypos+fm.getDescent());
242             g.setColor(Color.black);
243         }
244         else
245         {
246             drawNode(g, (SequenceNode) node.left(), chunk, scale, width, offx,
247                 offy);
248             drawNode(g, (SequenceNode) node.right(), chunk, scale, width, offx,
249                 offy);
250
251             float height = node.height;
252             float dist = node.dist;
253
254             int xstart = (int) ((height - dist) * scale) + offx;
255             int xend = (int) (height * scale) + offx;
256             int ypos = (int) (node.ycount * chunk) + offy;
257
258             g.setColor(((SequenceNode) node).color.darker());
259
260             // Draw horizontal line
261             g.drawLine(xstart, ypos, xend, ypos);
262             if (node == highlightNode)
263               g.fillRect(xend - 3, ypos - 3, 6, 6);
264             else
265               g.fillRect(xend - 2, ypos - 2, 4, 4);
266
267             int ystart = (int) (((SequenceNode) node.left()).ycount * chunk) +
268                 offy;
269             int yend = (int) (((SequenceNode) node.right()).ycount * chunk) +
270                 offy;
271
272             Rectangle pos = new Rectangle(xend - 2, ypos - 2, 5, 5);
273             nodeHash.put(node, pos);
274
275             g.drawLine((int) (height * scale) + offx, ystart,
276                 (int) (height * scale) + offx, yend);
277
278             if (showDistances && (node.dist > 0))
279             {
280                 g.drawString(new Format("%-.2f").form(node.dist).trim(), xstart+2,
281                     ypos - 2);
282             }
283         }
284     }
285
286     /**
287      * DOCUMENT ME!
288      *
289      * @param x DOCUMENT ME!
290      * @param y DOCUMENT ME!
291      *
292      * @return DOCUMENT ME!
293      */
294     public Object findElement(int x, int y)
295     {
296         Enumeration keys = nameHash.keys();
297
298         while (keys.hasMoreElements())
299         {
300             Object ob = keys.nextElement();
301             Rectangle rect = (Rectangle) nameHash.get(ob);
302
303             if ((x >= rect.x) && (x <= (rect.x + rect.width)) && (y >= rect.y) &&
304                     (y <= (rect.y + rect.height)))
305             {
306                 return ob;
307             }
308         }
309
310         keys = nodeHash.keys();
311
312         while (keys.hasMoreElements())
313         {
314             Object ob = keys.nextElement();
315             Rectangle rect = (Rectangle) nodeHash.get(ob);
316
317             if ((x >= rect.x) && (x <= (rect.x + rect.width)) && (y >= rect.y) &&
318                     (y <= (rect.y + rect.height)))
319             {
320                 return ob;
321             }
322         }
323
324         return null;
325     }
326
327     /**
328      * DOCUMENT ME!
329      *
330      * @param pickBox DOCUMENT ME!
331      */
332     public void pickNodes(Rectangle pickBox)
333     {
334         int width = getWidth();
335         int height = getHeight();
336
337         SequenceNode top = tree.getTopNode();
338
339         float wscale = (float) ((width * .8) - (offx * 2)) / tree.getMaxHeight();
340
341         if (top.count == 0)
342         {
343             top.count = ((SequenceNode) top.left()).count +
344                 ((SequenceNode) top.right()).count;
345         }
346
347         float chunk = (float) (height - (offy)) / top.count;
348
349         pickNode(pickBox, top, chunk, wscale, width, offx, offy);
350     }
351
352     /**
353      * DOCUMENT ME!
354      *
355      * @param pickBox DOCUMENT ME!
356      * @param node DOCUMENT ME!
357      * @param chunk DOCUMENT ME!
358      * @param scale DOCUMENT ME!
359      * @param width DOCUMENT ME!
360      * @param offx DOCUMENT ME!
361      * @param offy DOCUMENT ME!
362      */
363     public void pickNode(Rectangle pickBox, SequenceNode node, float chunk,
364         float scale, int width, int offx, int offy)
365     {
366         if (node == null)
367         {
368             return;
369         }
370
371         if ((node.left() == null) && (node.right() == null))
372         {
373             float height = node.height;
374             float dist = node.dist;
375
376             int xstart = (int) ((height - dist) * scale) + offx;
377             int xend = (int) (height * scale) + offx;
378
379             int ypos = (int) (node.ycount * chunk) + offy;
380
381             if (pickBox.contains(new Point(xend, ypos)))
382             {
383                 if (node.element() instanceof SequenceI)
384                 {
385                     SequenceI seq = (SequenceI) node.element();
386                     SequenceGroup sg = av.getSelectionGroup();
387
388                     if (sg != null)
389                     {
390                         sg.addOrRemove(seq, true);
391                     }
392                 }
393             }
394         }
395         else
396         {
397             pickNode(pickBox, (SequenceNode) node.left(), chunk, scale, width,
398                 offx, offy);
399             pickNode(pickBox, (SequenceNode) node.right(), chunk, scale, width,
400                 offx, offy);
401         }
402     }
403
404     /**
405      * DOCUMENT ME!
406      *
407      * @param node DOCUMENT ME!
408      * @param c DOCUMENT ME!
409      */
410     public void setColor(SequenceNode node, Color c)
411     {
412         if (node == null)
413         {
414             return;
415         }
416
417         if ((node.left() == null) && (node.right() == null))
418         {
419             node.color = c;
420
421             if (node.element() instanceof SequenceI)
422             {
423               av.setSequenceColour((SequenceI) node.element(), c);
424             }
425         }
426         else
427         {
428             node.color = c;
429             setColor((SequenceNode) node.left(), c);
430             setColor((SequenceNode) node.right(), c);
431         }
432     }
433
434     /**
435      * DOCUMENT ME!
436      */
437     void startPrinting()
438     {
439         Thread thread = new Thread(this);
440         thread.start();
441     }
442
443     // put printing in a thread to avoid painting problems
444     public void run()
445     {
446         PrinterJob printJob = PrinterJob.getPrinterJob();
447         PageFormat pf = printJob.pageDialog(printJob.defaultPage());
448
449         printJob.setPrintable(this, pf);
450
451         if (printJob.printDialog())
452         {
453             try
454             {
455                 printJob.print();
456             }
457             catch (Exception PrintException)
458             {
459                 PrintException.printStackTrace();
460             }
461         }
462     }
463
464     /**
465      * DOCUMENT ME!
466      *
467      * @param pg DOCUMENT ME!
468      * @param pf DOCUMENT ME!
469      * @param pi DOCUMENT ME!
470      *
471      * @return DOCUMENT ME!
472      *
473      * @throws PrinterException DOCUMENT ME!
474      */
475     public int print(Graphics pg, PageFormat pf, int pi)
476         throws PrinterException
477     {
478         pg.setFont(font);
479         pg.translate((int) pf.getImageableX(), (int) pf.getImageableY());
480
481         int pwidth = (int) pf.getImageableWidth();
482         int pheight = (int) pf.getImageableHeight();
483
484         int noPages = getHeight() / pheight;
485
486         if (pi > noPages)
487         {
488             return Printable.NO_SUCH_PAGE;
489         }
490
491         if (pwidth > getWidth())
492         {
493             pwidth = getWidth();
494         }
495
496         if (fitToWindow)
497         {
498             if (pheight > getHeight())
499             {
500                 pheight = getHeight();
501             }
502
503             noPages = 0;
504         }
505         else
506         {
507             FontMetrics fm = pg.getFontMetrics(font);
508             int height = fm.getHeight() * nameHash.size();
509             pg.translate(0, -pi * pheight);
510             pg.setClip(0, pi * pheight, pwidth, (pi * pheight) + pheight);
511
512             // translate number of pages,
513             // height is screen size as this is the
514             // non overlapping text size
515             pheight = height;
516         }
517
518         draw(pg, pwidth, pheight);
519
520         return Printable.PAGE_EXISTS;
521     }
522
523     /**
524      * DOCUMENT ME!
525      *
526      * @param g DOCUMENT ME!
527      */
528     public void paintComponent(Graphics g)
529     {
530         super.paintComponent(g);
531         g.setFont(font);
532
533         if(tree==null)
534         {
535           g.drawString("Calculating tree....", 20, getHeight()/2);
536         }
537         else
538         {
539           fm = g.getFontMetrics(font);
540
541           if (nameHash.size() == 0)
542           {
543             repaint();
544           }
545
546           if (fitToWindow ||
547               (!fitToWindow &&
548                (scrollPane.getHeight() > ( (fm.getHeight() * nameHash.size()) +
549                                           offy))))
550           {
551             draw(g, scrollPane.getWidth(), scrollPane.getHeight());
552             setPreferredSize(null);
553           }
554           else
555           {
556             setPreferredSize(new Dimension(scrollPane.getWidth(),
557                                            fm.getHeight() * nameHash.size()));
558             draw(g, scrollPane.getWidth(), fm.getHeight() * nameHash.size());
559           }
560
561           scrollPane.revalidate();
562         }
563     }
564
565
566     /**
567      * DOCUMENT ME!
568      *
569      * @param fontSize DOCUMENT ME!
570      */
571     public void setFont(Font font)
572     {
573         this.font = font;
574         repaint();
575     }
576
577     /**
578      * DOCUMENT ME!
579      *
580      * @param g1 DOCUMENT ME!
581      * @param width DOCUMENT ME!
582      * @param height DOCUMENT ME!
583      */
584     public void draw(Graphics g1, int width, int height)
585     {
586         Graphics2D g2 = (Graphics2D) g1;
587         g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
588             RenderingHints.VALUE_ANTIALIAS_ON);
589         g2.setColor(Color.white);
590         g2.fillRect(0, 0, width, height);
591
592         g2.setFont(font);
593
594         offy = font.getSize()+10;
595
596         fm = g2.getFontMetrics(font);
597
598         labelLength = fm.stringWidth(longestName) + 20; //20 allows for scrollbar
599
600         float wscale = (float) (width - labelLength - (offx * 2)) / tree.getMaxHeight();
601
602         SequenceNode top = tree.getTopNode();
603
604         if (top.count == 0)
605         {
606             top.count = ((SequenceNode) top.left()).count +
607                 ((SequenceNode) top.right()).count;
608         }
609
610         float chunk = (float) (height - (offy)) / top.count;
611
612         drawNode(g2, tree.getTopNode(), chunk, wscale, width, offx, offy);
613
614         if (threshold != 0)
615         {
616             if (av.getCurrentTree() == tree)
617             {
618                 g2.setColor(Color.red);
619             }
620             else
621             {
622                 g2.setColor(Color.gray);
623             }
624
625             int x = (int) ((threshold * (float) (getWidth() - labelLength -
626                 (2 * offx))) + offx);
627
628             g2.drawLine(x, 0, x, getHeight());
629         }
630     }
631
632     /**
633      * DOCUMENT ME!
634      *
635      * @param e DOCUMENT ME!
636      */
637     public void mouseReleased(MouseEvent e)
638     {
639     }
640
641     /**
642      * DOCUMENT ME!
643      *
644      * @param e DOCUMENT ME!
645      */
646     public void mouseEntered(MouseEvent e)
647     {
648     }
649
650     /**
651      * DOCUMENT ME!
652      *
653      * @param e DOCUMENT ME!
654      */
655     public void mouseExited(MouseEvent e)
656     {
657     }
658
659     /**
660      * DOCUMENT ME!
661      *
662      * @param e DOCUMENT ME!
663      */
664     public void mouseClicked(MouseEvent evt)
665     {
666       if(highlightNode!=null)
667       {
668         if (SwingUtilities.isRightMouseButton(evt))
669         {
670           Color col = JColorChooser.showDialog(this, "Select Sub-Tree Colour",
671                                                highlightNode.color);
672
673           setColor(highlightNode, col);
674         }
675         else
676         if(evt.getClickCount()>1)
677         {
678           tree.swapNodes(highlightNode);
679           tree.reCount(tree.getTopNode());
680           tree.findHeight(tree.getTopNode());
681         }
682         else
683         {
684           Vector leaves = new Vector();
685           tree.findLeaves(highlightNode, leaves);
686
687           for (int i = 0; i < leaves.size(); i++)
688           {
689             SequenceI seq =
690                 (SequenceI) ( (SequenceNode) leaves.elementAt(i)).element();
691             treeSelectionChanged(seq);
692           }
693         }
694
695         PaintRefresher.Refresh(this, av.getSequenceSetId());
696         repaint();
697       }
698     }
699
700
701
702     public void mouseMoved(MouseEvent evt)
703     {
704       av.setCurrentTree(tree);
705
706       Object ob = findElement(evt.getX(), evt.getY());
707
708       if (ob instanceof SequenceNode)
709       {
710         highlightNode = (SequenceNode) ob;
711         this.setToolTipText(
712             "<html>Left click to select leaves"
713             + "<br>Double-click to invert leaves"
714             + "<br>Right click to change colour");
715         repaint();
716
717       }
718       else
719       {
720         if (highlightNode != null)
721         {
722           highlightNode = null;
723           setToolTipText(null);
724           repaint();
725         }
726       }
727   }
728
729     public void mouseDragged(MouseEvent ect)
730     {}
731
732     /**
733      * DOCUMENT ME!
734      *
735      * @param e DOCUMENT ME!
736      */
737     public void mousePressed(MouseEvent e)
738     {
739         av.setCurrentTree(tree);
740
741         int x = e.getX();
742         int y = e.getY();
743
744         Object ob = findElement(x, y);
745
746         if (ob instanceof SequenceI)
747         {
748           treeSelectionChanged( (Sequence) ob);
749           PaintRefresher.Refresh(this, ap.av.getSequenceSetId());
750           repaint();
751           return;
752         }
753         else if( !(ob instanceof SequenceNode) )
754         {
755             // Find threshold
756             if (tree.getMaxHeight() != 0)
757             {
758                 threshold = (float) (x - offx) / (float) (getWidth() -
759                     labelLength - (2 * offx));
760
761                 tree.getGroups().removeAllElements();
762                 tree.groupNodes(tree.getTopNode(), threshold);
763                 setColor(tree.getTopNode(), Color.black);
764
765                 av.setSelectionGroup(null);
766                 av.alignment.deleteAllGroups();
767                 av.sequenceColours=null;
768
769                 colourGroups();
770               }
771
772               PaintRefresher.Refresh(this, ap.av.getSequenceSetId());
773               repaint();
774         }
775
776
777     }
778
779     void colourGroups()
780     {
781       for (int i = 0; i < tree.getGroups().size(); i++)
782       {
783         Color col = new Color( (int) (Math.random() * 255),
784                               (int) (Math.random() * 255),
785                               (int) (Math.random() * 255));
786         setColor( (SequenceNode) tree.getGroups().elementAt(i),
787                  col.brighter());
788
789         Vector l = tree.findLeaves( (SequenceNode) tree.getGroups()
790                                    .elementAt(i),
791                                    new Vector());
792
793         Vector sequences = new Vector();
794
795         for (int j = 0; j < l.size(); j++)
796         {
797           SequenceI s1 = (SequenceI) ( (SequenceNode) l.elementAt(j)).element();
798
799           if (!sequences.contains(s1))
800           {
801             sequences.addElement(s1);
802           }
803         }
804
805         ColourSchemeI cs = null;
806
807         if (av.getGlobalColourScheme() != null)
808         {
809           if (av.getGlobalColourScheme() instanceof UserColourScheme)
810           {
811             cs = new UserColourScheme(
812                 ( (UserColourScheme) av.getGlobalColourScheme()).getColours());
813
814           }
815           else
816             cs = ColourSchemeProperty.getColour(sequences,
817                                                 av.alignment.getWidth(),
818                                                 ColourSchemeProperty.
819                                                 getColourName(
820                                                     av.getGlobalColourScheme()));
821
822           cs.setThreshold(av.getGlobalColourScheme().getThreshold(),
823                           av.getIgnoreGapsConsensus());
824         }
825
826         SequenceGroup sg = new SequenceGroup(sequences,
827                                              "TreeGroup", cs, true, true, false,
828                                              0,
829                                              av.alignment.getWidth() - 1);
830
831         if (av.getGlobalColourScheme() != null
832             && av.getGlobalColourScheme().conservationApplied())
833         {
834           Conservation c = new Conservation("Group",
835                                             ResidueProperties.propHash, 3,
836                                             sg.getSequences(false),
837                                             sg.getStartRes(), sg.getEndRes());
838
839           c.calculate();
840           c.verdict(false, av.ConsPercGaps);
841           sg.cs.setConservation(c);
842         }
843
844         av.alignment.addGroup(sg);
845       }
846
847     }
848
849     /**
850      * DOCUMENT ME!
851      *
852      * @param state DOCUMENT ME!
853      */
854     public void setShowDistances(boolean state)
855     {
856         this.showDistances = state;
857         repaint();
858     }
859
860     /**
861      * DOCUMENT ME!
862      *
863      * @param state DOCUMENT ME!
864      */
865     public void setShowBootstrap(boolean state)
866     {
867         this.showBootstrap = state;
868         repaint();
869     }
870
871     /**
872      * DOCUMENT ME!
873      *
874      * @param state DOCUMENT ME!
875      */
876     public void setMarkPlaceholders(boolean state)
877     {
878         this.markPlaceholders = state;
879         repaint();
880     }
881 }