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