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