updated to jalview 2.1 and begun ArchiveClient/VamsasClient/VamsasStore updates.
[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
46 {
47     /** DOCUMENT ME!! */
48     public static final String PLACEHOLDER = " * ";
49     NJTree tree;
50     JScrollPane scrollPane;
51     AlignViewport av;
52     Font font;
53     FontMetrics fm;
54     boolean fitToWindow = true;
55     boolean showDistances = false;
56     boolean showBootstrap = false;
57     boolean markPlaceholders = false;
58     int offx = 20;
59     int offy;
60     float threshold;
61     String longestName;
62     int labelLength = -1;
63
64     //RubberbandRectangle rubberband;
65     Vector listeners;
66     Hashtable nameHash = new Hashtable();
67     Hashtable nodeHash = new Hashtable();
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(AlignViewport av, JScrollPane scroller)
78     {
79         this.av = av;
80         font = av.getFont();
81         scrollPane = scroller;
82         addMouseListener(this);
83         PaintRefresher.Register(this, av.alignment);
84     }
85
86
87     /**
88      * DOCUMENT ME!
89      *
90      * @param sequence DOCUMENT ME!
91      */
92     public void TreeSelectionChanged(Sequence sequence)
93     {
94         SequenceGroup selected = av.getSelectionGroup();
95
96         if (selected == null)
97         {
98             selected = new SequenceGroup();
99             av.setSelectionGroup(selected);
100         }
101
102         selected.setEndRes(av.alignment.getWidth()-1);
103         selected.addOrRemove(sequence, true);
104
105         PaintRefresher.Refresh(this, av.alignment);
106         repaint();
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                 if (((SequenceI) ((SequenceNode) node).element()).getColor() == Color.white)
177                 {
178                     g.setColor(Color.black);
179                 }
180                 else
181                 {
182                     g.setColor(((SequenceI) ((SequenceNode) node).element()).getColor()
183                                 .darker());
184                 }
185             }
186             else
187             {
188                 g.setColor(Color.black);
189             }
190
191             // Draw horizontal line
192             g.drawLine(xstart, ypos, xend, ypos);
193
194             String nodeLabel = "";
195
196             if (showDistances && (node.dist > 0))
197             {
198                 nodeLabel = new Format("%-.2f").form(node.dist);
199             }
200
201             if (showBootstrap)
202             {
203                 if (showDistances)
204                 {
205                     nodeLabel = nodeLabel + " : ";
206                 }
207
208                 nodeLabel = nodeLabel + String.valueOf(node.getBootstrap());
209             }
210
211             if (!nodeLabel.equals(""))
212             {
213                 g.drawString(nodeLabel, xstart+2, ypos - 2);
214             }
215
216             String name = (markPlaceholders && node.isPlaceholder())
217                 ? (PLACEHOLDER + node.getName()) : node.getName();
218
219             int charWidth = fm.stringWidth(name) + 3;
220             int charHeight = font.getSize();
221
222             Rectangle rect = new Rectangle(xend+10, ypos-charHeight/2,
223                     charWidth, charHeight);
224
225             nameHash.put((SequenceI) node.element(), rect);
226
227             // Colour selected leaves differently
228             SequenceGroup selected = av.getSelectionGroup();
229
230             if ((selected != null) &&
231                     selected.getSequences(false).contains((SequenceI) node.element()))
232             {
233                 g.setColor(Color.gray);
234
235                 g.fillRect(xend + 10, ypos-charHeight/2, charWidth,
236                     charHeight);
237                 g.setColor(Color.white);
238             }
239
240             g.drawString(name, xend + 10, ypos+fm.getDescent());
241             g.setColor(Color.black);
242         }
243         else
244         {
245             drawNode(g, (SequenceNode) node.left(), chunk, scale, width, offx,
246                 offy);
247             drawNode(g, (SequenceNode) node.right(), chunk, scale, width, offx,
248                 offy);
249
250             float height = node.height;
251             float dist = node.dist;
252
253             int xstart = (int) ((height - dist) * scale) + offx;
254             int xend = (int) (height * scale) + offx;
255             int ypos = (int) (node.ycount * chunk) + offy;
256
257             g.setColor(((SequenceNode) node).color.darker());
258
259             // Draw horizontal line
260             g.drawLine(xstart, ypos, xend, ypos);
261             g.fillRect(xend - 2, ypos - 2, 4, 4);
262
263             int ystart = (int) (((SequenceNode) node.left()).ycount * chunk) +
264                 offy;
265             int yend = (int) (((SequenceNode) node.right()).ycount * chunk) +
266                 offy;
267
268             Rectangle pos = new Rectangle(xend - 2, ypos - 2, 5, 5);
269             nodeHash.put(node, pos);
270
271             g.drawLine((int) (height * scale) + offx, ystart,
272                 (int) (height * scale) + offx, yend);
273
274             if (showDistances && (node.dist > 0))
275             {
276                 g.drawString(new Format("%-.2f").form(node.dist).trim(), xstart+2,
277                     ypos - 2);
278             }
279         }
280     }
281
282     /**
283      * DOCUMENT ME!
284      *
285      * @param x DOCUMENT ME!
286      * @param y DOCUMENT ME!
287      *
288      * @return DOCUMENT ME!
289      */
290     public Object findElement(int x, int y)
291     {
292         Enumeration keys = nameHash.keys();
293
294         while (keys.hasMoreElements())
295         {
296             Object ob = keys.nextElement();
297             Rectangle rect = (Rectangle) nameHash.get(ob);
298
299             if ((x >= rect.x) && (x <= (rect.x + rect.width)) && (y >= rect.y) &&
300                     (y <= (rect.y + rect.height)))
301             {
302                 return ob;
303             }
304         }
305
306         keys = nodeHash.keys();
307
308         while (keys.hasMoreElements())
309         {
310             Object ob = keys.nextElement();
311             Rectangle rect = (Rectangle) nodeHash.get(ob);
312
313             if ((x >= rect.x) && (x <= (rect.x + rect.width)) && (y >= rect.y) &&
314                     (y <= (rect.y + rect.height)))
315             {
316                 return ob;
317             }
318         }
319
320         return null;
321     }
322
323     /**
324      * DOCUMENT ME!
325      *
326      * @param pickBox DOCUMENT ME!
327      */
328     public void pickNodes(Rectangle pickBox)
329     {
330         int width = getWidth();
331         int height = getHeight();
332
333         SequenceNode top = tree.getTopNode();
334
335         float wscale = (float) ((width * .8) - (offx * 2)) / tree.getMaxHeight();
336
337         if (top.count == 0)
338         {
339             top.count = ((SequenceNode) top.left()).count +
340                 ((SequenceNode) top.right()).count;
341         }
342
343         float chunk = (float) (height - (offy)) / top.count;
344
345         pickNode(pickBox, top, chunk, wscale, width, offx, offy);
346     }
347
348     /**
349      * DOCUMENT ME!
350      *
351      * @param pickBox DOCUMENT ME!
352      * @param node DOCUMENT ME!
353      * @param chunk DOCUMENT ME!
354      * @param scale DOCUMENT ME!
355      * @param width DOCUMENT ME!
356      * @param offx DOCUMENT ME!
357      * @param offy DOCUMENT ME!
358      */
359     public void pickNode(Rectangle pickBox, SequenceNode node, float chunk,
360         float scale, int width, int offx, int offy)
361     {
362         if (node == null)
363         {
364             return;
365         }
366
367         if ((node.left() == null) && (node.right() == null))
368         {
369             float height = node.height;
370             float dist = node.dist;
371
372             int xstart = (int) ((height - dist) * scale) + offx;
373             int xend = (int) (height * scale) + offx;
374
375             int ypos = (int) (node.ycount * chunk) + offy;
376
377             if (pickBox.contains(new Point(xend, ypos)))
378             {
379                 if (node.element() instanceof SequenceI)
380                 {
381                     SequenceI seq = (SequenceI) node.element();
382                     SequenceGroup sg = av.getSelectionGroup();
383
384                     if (sg != null)
385                     {
386                         sg.addOrRemove(seq, true);
387                     }
388                 }
389             }
390         }
391         else
392         {
393             pickNode(pickBox, (SequenceNode) node.left(), chunk, scale, width,
394                 offx, offy);
395             pickNode(pickBox, (SequenceNode) node.right(), chunk, scale, width,
396                 offx, offy);
397         }
398     }
399
400     /**
401      * DOCUMENT ME!
402      *
403      * @param node DOCUMENT ME!
404      * @param c DOCUMENT ME!
405      */
406     public void setColor(SequenceNode node, Color c)
407     {
408         if (node == null)
409         {
410             return;
411         }
412
413         if ((node.left() == null) && (node.right() == null))
414         {
415             node.color = c;
416
417             if (node.element() instanceof SequenceI)
418             {
419                 ((SequenceI) node.element()).setColor(c);
420             }
421         }
422         else
423         {
424             node.color = c;
425             setColor((SequenceNode) node.left(), c);
426             setColor((SequenceNode) node.right(), c);
427         }
428     }
429
430     /**
431      * DOCUMENT ME!
432      */
433     void startPrinting()
434     {
435         Thread thread = new Thread(this);
436         thread.start();
437     }
438
439     // put printing in a thread to avoid painting problems
440     public void run()
441     {
442         PrinterJob printJob = PrinterJob.getPrinterJob();
443         PageFormat pf = printJob.pageDialog(printJob.defaultPage());
444
445         printJob.setPrintable(this, pf);
446
447         if (printJob.printDialog())
448         {
449             try
450             {
451                 printJob.print();
452             }
453             catch (Exception PrintException)
454             {
455                 PrintException.printStackTrace();
456             }
457         }
458     }
459
460     /**
461      * DOCUMENT ME!
462      *
463      * @param pg DOCUMENT ME!
464      * @param pf DOCUMENT ME!
465      * @param pi DOCUMENT ME!
466      *
467      * @return DOCUMENT ME!
468      *
469      * @throws PrinterException DOCUMENT ME!
470      */
471     public int print(Graphics pg, PageFormat pf, int pi)
472         throws PrinterException
473     {
474         pg.setFont(font);
475         pg.translate((int) pf.getImageableX(), (int) pf.getImageableY());
476
477         int pwidth = (int) pf.getImageableWidth();
478         int pheight = (int) pf.getImageableHeight();
479
480         int noPages = getHeight() / pheight;
481
482         if (pi > noPages)
483         {
484             return Printable.NO_SUCH_PAGE;
485         }
486
487         if (pwidth > getWidth())
488         {
489             pwidth = getWidth();
490         }
491
492         if (fitToWindow)
493         {
494             if (pheight > getHeight())
495             {
496                 pheight = getHeight();
497             }
498
499             noPages = 0;
500         }
501         else
502         {
503             FontMetrics fm = pg.getFontMetrics(font);
504             int height = fm.getHeight() * nameHash.size();
505             pg.translate(0, -pi * pheight);
506             pg.setClip(0, pi * pheight, pwidth, (pi * pheight) + pheight);
507
508             // translate number of pages,
509             // height is screen size as this is the
510             // non overlapping text size
511             pheight = height;
512         }
513
514         draw(pg, pwidth, pheight);
515
516         return Printable.PAGE_EXISTS;
517     }
518
519     /**
520      * DOCUMENT ME!
521      *
522      * @param g DOCUMENT ME!
523      */
524     public void paintComponent(Graphics g)
525     {
526         super.paintComponent(g);
527         g.setFont(font);
528
529         if(tree==null)
530         {
531           g.drawString("Calculating tree....", 20, getHeight()/2);
532         }
533         else
534         {
535           fm = g.getFontMetrics(font);
536
537           if (nameHash.size() == 0)
538           {
539             repaint();
540           }
541
542           if (fitToWindow ||
543               (!fitToWindow &&
544                (scrollPane.getHeight() > ( (fm.getHeight() * nameHash.size()) +
545                                           offy))))
546           {
547             draw(g, scrollPane.getWidth(), scrollPane.getHeight());
548             setPreferredSize(null);
549           }
550           else
551           {
552             setPreferredSize(new Dimension(scrollPane.getWidth(),
553                                            fm.getHeight() * nameHash.size()));
554             draw(g, scrollPane.getWidth(), fm.getHeight() * nameHash.size());
555           }
556
557           scrollPane.revalidate();
558         }
559     }
560
561
562     /**
563      * DOCUMENT ME!
564      *
565      * @param fontSize DOCUMENT ME!
566      */
567     public void setFont(Font font)
568     {
569         this.font = font;
570         repaint();
571     }
572
573     /**
574      * DOCUMENT ME!
575      *
576      * @param g1 DOCUMENT ME!
577      * @param width DOCUMENT ME!
578      * @param height DOCUMENT ME!
579      */
580     public void draw(Graphics g1, int width, int height)
581     {
582         Graphics2D g2 = (Graphics2D) g1;
583         g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
584             RenderingHints.VALUE_ANTIALIAS_ON);
585         g2.setColor(Color.white);
586         g2.fillRect(0, 0, width, height);
587
588         g2.setFont(font);
589
590         offy = font.getSize()+10;
591
592         fm = g2.getFontMetrics(font);
593
594         labelLength = fm.stringWidth(longestName) + 20; //20 allows for scrollbar
595
596         float wscale = (float) (width - labelLength - (offx * 2)) / tree.getMaxHeight();
597
598         SequenceNode top = tree.getTopNode();
599
600         if (top.count == 0)
601         {
602             top.count = ((SequenceNode) top.left()).count +
603                 ((SequenceNode) top.right()).count;
604         }
605
606         float chunk = (float) (height - (offy)) / top.count;
607
608         drawNode(g2, tree.getTopNode(), chunk, wscale, width, offx, offy);
609
610         if (threshold != 0)
611         {
612             if (av.getCurrentTree() == tree)
613             {
614                 g2.setColor(Color.red);
615             }
616             else
617             {
618                 g2.setColor(Color.gray);
619             }
620
621             int x = (int) ((threshold * (float) (getWidth() - labelLength -
622                 (2 * offx))) + offx);
623
624             g2.drawLine(x, 0, x, getHeight());
625         }
626     }
627
628     /**
629      * DOCUMENT ME!
630      *
631      * @param e DOCUMENT ME!
632      */
633     public void mouseReleased(MouseEvent e)
634     {
635     }
636
637     /**
638      * DOCUMENT ME!
639      *
640      * @param e DOCUMENT ME!
641      */
642     public void mouseEntered(MouseEvent e)
643     {
644     }
645
646     /**
647      * DOCUMENT ME!
648      *
649      * @param e DOCUMENT ME!
650      */
651     public void mouseExited(MouseEvent e)
652     {
653     }
654
655     /**
656      * DOCUMENT ME!
657      *
658      * @param e DOCUMENT ME!
659      */
660     public void mouseClicked(MouseEvent e)
661     {
662     }
663
664     /**
665      * DOCUMENT ME!
666      *
667      * @param e DOCUMENT ME!
668      */
669     public void mousePressed(MouseEvent e)
670     {
671         av.setCurrentTree(tree);
672
673         int x = e.getX();
674         int y = e.getY();
675
676         Object ob = findElement(x, y);
677
678         if (ob instanceof SequenceI)
679         {
680             TreeSelectionChanged((Sequence) ob);
681             repaint();
682
683             return;
684         }
685         else if (ob instanceof SequenceNode)
686         {
687             SequenceNode tmpnode = (SequenceNode) ob;
688             tree.swapNodes(tmpnode);
689             tree.reCount(tree.getTopNode());
690             tree.findHeight(tree.getTopNode());
691         }
692         else
693         {
694             // Find threshold
695             if (tree.getMaxHeight() != 0)
696             {
697                 threshold = (float) (x - offx) / (float) (getWidth() -
698                     labelLength - (2 * offx));
699
700                 tree.getGroups().removeAllElements();
701                 tree.groupNodes(tree.getTopNode(), threshold);
702                 setColor(tree.getTopNode(), Color.black);
703
704                 av.setSelectionGroup(null);
705                 av.alignment.deleteAllGroups();
706
707                 for (int i = 0; i < tree.getGroups().size(); i++)
708                 {
709                     Color col = new Color((int) (Math.random() * 255),
710                             (int) (Math.random() * 255),
711                             (int) (Math.random() * 255));
712                     setColor((SequenceNode) tree.getGroups().elementAt(i),
713                         col.brighter());
714
715                     Vector l = tree.findLeaves((SequenceNode) tree.getGroups()
716                                                                   .elementAt(i),
717                             new Vector());
718
719                     Vector sequences = new Vector();
720
721                     for (int j = 0; j < l.size(); j++)
722                     {
723                         SequenceI s1 = (SequenceI) ((SequenceNode) l.elementAt(j)).element();
724
725                         if (!sequences.contains(s1))
726                         {
727                             sequences.addElement(s1);
728                         }
729                     }
730
731                     ColourSchemeI cs = null;
732
733                     if (av.getGlobalColourScheme() != null)
734                     {
735                       if (av.getGlobalColourScheme() instanceof UserColourScheme)
736                       {
737                         cs = new UserColourScheme(
738                             ( (UserColourScheme) av.getGlobalColourScheme()).getColours());
739
740                       }
741                       else
742                         cs = ColourSchemeProperty.getColour(sequences,
743                                                             av.alignment.getWidth(),
744                                                             ColourSchemeProperty.getColourName(
745                                                                 av.getGlobalColourScheme()));
746
747                         cs.setThreshold(av.getGlobalColourScheme().getThreshold(),
748                                                              av.getIgnoreGapsConsensus());
749                     }
750
751
752                     SequenceGroup sg = new SequenceGroup(sequences,
753                             "TreeGroup", cs, true, true, false, 0,
754                             av.alignment.getWidth()-1);
755
756
757                     if (  av.getGlobalColourScheme()!=null
758                        && av.getGlobalColourScheme().conservationApplied())
759                     {
760                         Conservation c = new Conservation("Group",
761                                 ResidueProperties.propHash, 3,
762                                 sg.getSequences(false),
763                                 sg.getStartRes(), sg.getEndRes());
764
765                         c.calculate();
766                         c.verdict(false, av.ConsPercGaps);
767                         sg.cs.setConservation(c);
768                     }
769
770                     av.alignment.addGroup(sg);
771                 }
772             }
773         }
774
775         PaintRefresher.Refresh(this, av.alignment);
776         repaint();
777     }
778
779     /**
780      * DOCUMENT ME!
781      *
782      * @param state DOCUMENT ME!
783      */
784     public void setShowDistances(boolean state)
785     {
786         this.showDistances = state;
787         repaint();
788     }
789
790     /**
791      * DOCUMENT ME!
792      *
793      * @param state DOCUMENT ME!
794      */
795     public void setShowBootstrap(boolean state)
796     {
797         this.showBootstrap = state;
798         repaint();
799     }
800
801     /**
802      * DOCUMENT ME!
803      *
804      * @param state DOCUMENT ME!
805      */
806     public void setMarkPlaceholders(boolean state)
807     {
808         this.markPlaceholders = state;
809         repaint();
810     }
811 }