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