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