5ebf323be7e10d75b14ad580c163c8109fbb72e7
[jalview.git] / src / jalview / gui / TreeCanvas.java
1 /*\r
2 * Jalview - A Sequence Alignment Editor and Viewer\r
3 * Copyright (C) 2005 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle\r
4 *\r
5 * This program is free software; you can redistribute it and/or\r
6 * modify it under the terms of the GNU General Public License\r
7 * as published by the Free Software Foundation; either version 2\r
8 * of the License, or (at your option) any later version.\r
9 *\r
10 * This program is distributed in the hope that it will be useful,\r
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
13 * GNU General Public License for more details.\r
14 *\r
15 * You should have received a copy of the GNU General Public License\r
16 * along with this program; if not, write to the Free Software\r
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA\r
18 */\r
19 \r
20 package jalview.gui;\r
21 \r
22 \r
23 import jalview.analysis.*;\r
24 import jalview.datamodel.*;\r
25 import jalview.util.*;\r
26 import jalview.schemes.*;\r
27 import javax.swing.*;\r
28 import java.awt.*;\r
29 import java.awt.event.*;\r
30 import java.util.*;\r
31 import java.awt.print.*;\r
32 \r
33 public class TreeCanvas extends JPanel implements MouseListener, Runnable, Printable\r
34 {\r
35   NJTree tree;\r
36   JScrollPane scrollPane;\r
37   AlignViewport av;\r
38   public static final String PLACEHOLDER=" * ";\r
39   Font font;\r
40   int  fontSize = 12;\r
41 \r
42   boolean fitToWindow = true;\r
43   boolean showDistances = false;\r
44   boolean showBootstrap = false;\r
45   boolean markPlaceholders = false;\r
46 \r
47   int offx = 20;\r
48   int offy = 20;\r
49 \r
50   float threshold;\r
51 \r
52   String longestName;\r
53   int labelLength=-1;\r
54 \r
55   //RubberbandRectangle rubberband;\r
56 \r
57   Vector    listeners;\r
58 \r
59   Hashtable nameHash = new Hashtable();\r
60   Hashtable nodeHash = new Hashtable();\r
61 \r
62   public TreeCanvas(AlignViewport av, NJTree tree, JScrollPane scroller, String label)\r
63   {\r
64     this.av = av;\r
65     this.tree     = tree;\r
66     scrollPane = scroller;\r
67     addMouseListener(this);\r
68     tree.findHeight(tree.getTopNode());\r
69     longestName = label;\r
70 \r
71     PaintRefresher.Register(this);\r
72   }\r
73   public void TreeSelectionChanged(Sequence sequence)\r
74  {\r
75     SequenceGroup selected = av.getSelectionGroup();\r
76     if(selected == null)\r
77     {\r
78       selected = new SequenceGroup();\r
79       av.setSelectionGroup(selected);\r
80     }\r
81 \r
82     selected.setEndRes(av.alignment.getWidth());\r
83     selected.addOrRemove(sequence);\r
84 \r
85 \r
86     PaintRefresher.Refresh(this);\r
87     repaint();\r
88  }\r
89 \r
90 \r
91 \r
92   public void setTree(NJTree tree) {\r
93     this.tree = tree;\r
94     tree.findHeight(tree.getTopNode());\r
95   }\r
96 \r
97     public void drawNode(Graphics g,SequenceNode node, float chunk, float scale, int width,int offx, int offy) {\r
98     if (node == null) {\r
99       return;\r
100     }\r
101 \r
102     if (node.left() == null && node.right() == null) {\r
103       // Drawing leaf node\r
104 \r
105       float height = node.height;\r
106       float dist   = node.dist;\r
107 \r
108       int xstart = (int)((height-dist)*scale) + offx;\r
109       int xend =   (int)(height*scale)        + offx;\r
110 \r
111       int ypos = (int)(node.ycount * chunk) + offy;\r
112 \r
113       if (node.element() instanceof SequenceI)\r
114       {\r
115        if ( ( (SequenceI) ( (SequenceNode) node).element()).getColor() == Color.white)\r
116        {\r
117          g.setColor(Color.black);\r
118        }\r
119        else\r
120         g.setColor( ( (SequenceI) ( (SequenceNode) node).element()).getColor().\r
121                    darker());\r
122 \r
123       }\r
124       else\r
125           g.setColor(Color.black);\r
126 \r
127 \r
128       // Draw horizontal line\r
129       g.drawLine(xstart,ypos,xend,ypos);\r
130 \r
131       String nodeLabel = "";\r
132       if (showDistances && node.dist > 0) {\r
133         nodeLabel = new Format("%5.2f").form(node.dist);\r
134       }\r
135       if (showBootstrap) {\r
136         if (showDistances) {\r
137           nodeLabel = nodeLabel + " : ";\r
138         }\r
139         nodeLabel = nodeLabel + String.valueOf(node.getBootstrap());\r
140       }\r
141       if (! nodeLabel.equals("")) {\r
142       g.drawString(nodeLabel,xstart,ypos - 10);\r
143       }\r
144 \r
145       String name    = (markPlaceholders && node.isPlaceholder()) ? (PLACEHOLDER+node.getName()) : node.getName();\r
146       FontMetrics fm = g.getFontMetrics(font);\r
147       int charWidth  = fm.stringWidth(name) + 3;\r
148       int charHeight = fm.getHeight();\r
149 \r
150       Rectangle rect = new Rectangle(xend+20,ypos-charHeight,\r
151                                      charWidth,charHeight);\r
152 \r
153       nameHash.put((SequenceI)node.element(),rect);\r
154 \r
155       // Colour selected leaves differently\r
156       SequenceGroup selected = av.getSelectionGroup();\r
157       if (selected!=null && selected.sequences.contains((SequenceI)node.element())) {\r
158         g.setColor(Color.gray);\r
159 \r
160         g.fillRect(xend + 10, ypos - charHeight + 3,charWidth,charHeight);\r
161         g.setColor(Color.white);\r
162       }\r
163       g.drawString(name,xend+10,ypos);\r
164       g.setColor(Color.black);\r
165     } else {\r
166       drawNode(g,(SequenceNode)node.left(), chunk,scale,width,offx,offy);\r
167       drawNode(g,(SequenceNode)node.right(),chunk,scale,width,offx,offy);\r
168 \r
169       float height = node.height;\r
170       float dist   = node.dist;\r
171 \r
172       int xstart = (int)((height-dist)*scale) + offx;\r
173       int xend   = (int)(height       *scale) + offx;\r
174       int ypos   = (int)(node.ycount  *chunk) + offy;\r
175 \r
176       g.setColor(((SequenceNode)node).color.darker());\r
177 \r
178       // Draw horizontal line\r
179       g.drawLine(xstart,ypos,xend,ypos);\r
180       g.fillRect(xend-2, ypos-2, 4,4);\r
181 \r
182       int ystart = (int)(((SequenceNode)node.left()) .ycount * chunk) + offy;\r
183       int yend   = (int)(((SequenceNode)node.right()).ycount * chunk) + offy;\r
184 \r
185       Rectangle pos = new Rectangle(xend-2,ypos-2,5,5);\r
186       nodeHash.put(node,pos);\r
187 \r
188       g.drawLine((int)(height*scale) + offx, ystart,\r
189                  (int)(height*scale) + offx, yend);\r
190 \r
191       if (showDistances && node.dist > 0) {\r
192         g.drawString(new Format("%5.2f").form(node.dist),xstart,ypos - 5);\r
193       }\r
194 \r
195     }\r
196   }\r
197   public Object findElement(int x, int y) {\r
198        Enumeration keys = nameHash.keys();\r
199 \r
200     while (keys.hasMoreElements()) {\r
201             Object ob = keys.nextElement();\r
202             Rectangle rect = (Rectangle)nameHash.get(ob);\r
203 \r
204             if (x >= rect.x && x <= (rect.x + rect.width) &&\r
205               y >= rect.y && y <= (rect.y + rect.height)) {\r
206               return ob;\r
207             }\r
208   }\r
209   keys = nodeHash.keys();\r
210 \r
211     while (keys.hasMoreElements()) {\r
212             Object ob = keys.nextElement();\r
213             Rectangle rect = (Rectangle)nodeHash.get(ob);\r
214 \r
215             if (x >= rect.x && x <= (rect.x + rect.width) &&\r
216               y >= rect.y && y <= (rect.y + rect.height)) {\r
217               return ob;\r
218             }\r
219     }\r
220       return null;\r
221 \r
222   }\r
223 \r
224   public void pickNodes(Rectangle pickBox) {\r
225     int width  = getWidth();\r
226     int height = getHeight();\r
227 \r
228     SequenceNode top = tree.getTopNode();\r
229 \r
230     float wscale = (float)(width*.8-offx*2)/tree.getMaxHeight()\r
231 ;\r
232     if (top.count == 0) {\r
233       top.count = ((SequenceNode)top.left()).count + ((SequenceNode)top.right()).count ;\r
234     }\r
235     float chunk = (float)(height-offy*2)/top.count;\r
236 \r
237     pickNode(pickBox,top,chunk,wscale,width,offx,offy);\r
238   }\r
239 \r
240   public void pickNode(Rectangle pickBox, SequenceNode node, float chunk, float scale, int width,int offx, int offy) {\r
241     if (node == null) {\r
242       return;\r
243     }\r
244 \r
245     if (node.left() == null && node.right() == null) {\r
246       float height = node.height;\r
247       float dist   = node.dist;\r
248 \r
249       int xstart = (int)((height-dist)*scale) + offx;\r
250       int xend   = (int)(height*scale) + offx;\r
251 \r
252       int ypos = (int)(node.ycount * chunk) + offy;\r
253 \r
254       if (pickBox.contains(new Point(xend,ypos))) {\r
255         if (node.element() instanceof SequenceI) {\r
256           SequenceI seq = (SequenceI)node.element();\r
257           SequenceGroup sg = av.getSelectionGroup();\r
258           if(sg!=null)\r
259             sg.addOrRemove(seq);\r
260         }\r
261       }\r
262     } else {\r
263       pickNode(pickBox,(SequenceNode)node.left(), chunk,scale,width,offx,offy);\r
264       pickNode(pickBox,(SequenceNode)node.right(),chunk,scale,width,offx,offy);\r
265     }\r
266   }\r
267 \r
268   public void setColor(SequenceNode node, Color c)\r
269   {\r
270     if (node == null)\r
271       return;\r
272 \r
273     if (node.left() == null && node.right() == null)\r
274     {\r
275       node.color = c;\r
276 \r
277       if (node.element() instanceof SequenceI)\r
278           ((SequenceI)node.element()).setColor(c);\r
279     } else\r
280     {\r
281       node.color = c;\r
282       setColor((SequenceNode)node.left(),c);\r
283       setColor((SequenceNode)node.right(),c);\r
284     }\r
285   }\r
286 \r
287   void startPrinting()\r
288   {\r
289     Thread thread = new Thread(this);\r
290     thread.start();\r
291   }\r
292 \r
293   // put printing in a thread to avoid painting problems\r
294   public void run()\r
295   {\r
296     PrinterJob printJob = PrinterJob.getPrinterJob();\r
297     PageFormat pf = printJob.pageDialog(printJob.defaultPage());\r
298 \r
299     printJob.setPrintable(this, pf);\r
300     if (printJob.printDialog())\r
301     {\r
302       try\r
303       {\r
304         printJob.print();\r
305       }\r
306       catch (Exception PrintException)\r
307       {\r
308         PrintException.printStackTrace();\r
309       }\r
310     }\r
311   }\r
312 \r
313 \r
314   public int print(Graphics pg, PageFormat pf, int pi) throws PrinterException\r
315   {\r
316 \r
317     pg.setFont(font);\r
318     pg.translate((int)pf.getImageableX(), (int)pf.getImageableY());\r
319     int pwidth = (int) pf.getImageableWidth();\r
320     int pheight = (int) pf.getImageableHeight();\r
321 \r
322     int noPages = getHeight() / pheight;\r
323     if(pi>noPages)\r
324       return Printable.NO_SUCH_PAGE;\r
325 \r
326 \r
327     if (pwidth > getWidth())\r
328         pwidth = getWidth();\r
329 \r
330     if(fitToWindow)\r
331     {\r
332       if (pheight > getHeight())\r
333         pheight = getHeight();\r
334 \r
335       noPages = 0;\r
336     }\r
337     else\r
338     {\r
339 \r
340         FontMetrics fm = pg.getFontMetrics(font);\r
341         int height = fm.getHeight() * nameHash.size();\r
342         pg.translate(0, -pi*pheight  );\r
343         pg.setClip(0,pi*pheight, pwidth,pi*pheight + pheight);\r
344        // translate number of pages,\r
345        // height is screen size as this is the\r
346        // non overlapping text size\r
347         pheight = height;\r
348     }\r
349 \r
350     draw(pg, pwidth, pheight);\r
351 \r
352     return Printable.PAGE_EXISTS;\r
353 \r
354   }\r
355 \r
356   public void paintComponent(Graphics g)\r
357   {\r
358 \r
359     font = new Font("Verdana",Font.PLAIN,fontSize);\r
360     g.setFont(font);\r
361 \r
362     FontMetrics fm = g.getFontMetrics(font);\r
363 \r
364     if(nameHash.size()==0)\r
365       repaint();\r
366 \r
367 \r
368     if( fitToWindow || (!fitToWindow && scrollPane.getHeight() > fm.getHeight() * nameHash.size()+offy ) )\r
369      {\r
370          draw(g,scrollPane.getWidth(),scrollPane.getHeight());\r
371          setPreferredSize(null);\r
372      }\r
373     else\r
374      {\r
375          setPreferredSize(new Dimension(scrollPane.getWidth(), fm.getHeight() * nameHash.size()));\r
376          draw( g,scrollPane.getWidth(), fm.getHeight() * nameHash.size());\r
377      }\r
378 \r
379     scrollPane.revalidate();\r
380   }\r
381     public int getFontSize() {\r
382         return fontSize;\r
383     }\r
384     public void setFontSize(int fontSize) {\r
385         this.fontSize = fontSize;\r
386         repaint();\r
387     }\r
388   public void draw(Graphics g1, int width, int height) {\r
389 \r
390       Graphics2D g2 = (Graphics2D)g1;\r
391       g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);\r
392       g2.setColor(Color.white);\r
393       g2.fillRect(0,0,width,height);\r
394 \r
395 \r
396       labelLength = g2.getFontMetrics(font).stringWidth(longestName)+ 20;//20 allows for scrollbar\r
397 \r
398       float wscale =(float)(width - labelLength -offx*2)/tree.getMaxHeight();\r
399 \r
400       SequenceNode top = tree.getTopNode();\r
401 \r
402       if (top.count == 0) {\r
403           top.count = ((SequenceNode)top.left()).count + ((SequenceNode)top.right()).count ;\r
404       }\r
405       float chunk = (float)(height-offy*2)/top.count ;\r
406 \r
407       drawNode(g2,tree.getTopNode(),chunk,wscale,width,offx,offy);\r
408 \r
409       if (threshold != 0)\r
410       {\r
411         if(av.getCurrentTree() == tree)\r
412           g2.setColor(Color.red);\r
413         else\r
414           g2.setColor(Color.gray);\r
415 \r
416           int x = (int)(    threshold * (float)(getWidth()-labelLength - 2*offx) +offx   ) ;\r
417 \r
418           g2.drawLine(x,0,x,getHeight());\r
419       }\r
420 \r
421   }\r
422 \r
423   public void mouseReleased(MouseEvent e) { }\r
424   public void mouseEntered(MouseEvent e) { }\r
425   public void mouseExited(MouseEvent e) { }\r
426   public void mouseClicked(MouseEvent e) {\r
427   }\r
428 \r
429   public void mousePressed(MouseEvent e) {\r
430 \r
431       av.setCurrentTree(tree);\r
432 \r
433       int x = e.getX();\r
434       int y = e.getY();\r
435 \r
436       Object ob = findElement(x,y);\r
437 \r
438       if (ob instanceof SequenceI)\r
439       {\r
440           TreeSelectionChanged((Sequence)ob);\r
441           repaint();\r
442           return;\r
443 \r
444       } else if (ob instanceof SequenceNode) {\r
445           SequenceNode tmpnode = (SequenceNode)ob;\r
446           tree.swapNodes(tmpnode);\r
447           tree.reCount(tree.getTopNode());\r
448           tree.findHeight(tree.getTopNode());\r
449       } else {\r
450           // Find threshold\r
451 \r
452           if (tree.getMaxHeight() != 0) {\r
453               threshold = (float)(x - offx)/(float)(getWidth()-labelLength - 2*offx);\r
454 \r
455               tree.getGroups().removeAllElements();\r
456               tree.groupNodes(tree.getTopNode(),threshold);\r
457               setColor(tree.getTopNode(),Color.black);\r
458 \r
459               av.setSelectionGroup(null);\r
460               av.alignment.deleteAllGroups();\r
461 \r
462               for (int i=0; i < tree.getGroups().size(); i++)\r
463               {\r
464 \r
465                   Color col = new Color((int)(Math.random()*255),\r
466                                         (int)(Math.random()*255),\r
467                                         (int)(Math.random()*255));\r
468                   setColor((SequenceNode)tree.getGroups().elementAt(i),col.brighter());\r
469 \r
470                   Vector l = tree.findLeaves((SequenceNode)tree.getGroups().elementAt(i),new Vector());\r
471                   SequenceGroup sg = null;\r
472                   for (int j = 0; j < l.size(); j++)\r
473                   {\r
474                     SequenceNode sn = (SequenceNode) l.elementAt(j);\r
475                     if(sg==null)\r
476                        sg = new SequenceGroup("TreeGroup", av.getGlobalColourScheme(), true, true,false,0,av.alignment.getWidth());\r
477 \r
478                     sg.addSequence( (Sequence) sn.element());\r
479                   }\r
480 \r
481                   if (av.getGlobalColourScheme() instanceof ConservationColourScheme)\r
482                   {\r
483                     ConservationColourScheme ccs = (ConservationColourScheme) av.getGlobalColourScheme();\r
484                     Conservation c = new Conservation("Group",\r
485                                                       ResidueProperties.propHash, 3,\r
486                                                       sg.sequences, sg.getStartRes(),\r
487                                                       sg.getEndRes());\r
488 \r
489                     c.calculate();\r
490                     c.verdict(false, av.ConsPercGaps);\r
491                     ccs = new ConservationColourScheme(c, ccs.cs);\r
492 \r
493                     sg.cs = ccs;\r
494 \r
495                   }\r
496 \r
497 \r
498 \r
499                   av.alignment.addGroup(sg);\r
500 \r
501               }\r
502           }\r
503       }\r
504 \r
505       PaintRefresher.Refresh(this);\r
506       repaint();\r
507 \r
508   }\r
509 \r
510     public void setShowDistances(boolean state) {\r
511         this.showDistances = state;\r
512         repaint();\r
513     }\r
514 \r
515     public void setShowBootstrap(boolean state) {\r
516       this.showBootstrap = state;\r
517       repaint();\r
518     }\r
519     public void setMarkPlaceholders(boolean state) {\r
520             this.markPlaceholders = state;\r
521             repaint();\r
522     }\r
523 \r
524 }\r
525 \r