dont add sequence twice to new group
[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, av.alignment);\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, true);\r
83 \r
84     PaintRefresher.Refresh(this, av.alignment);\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, true);\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     super.paintComponent(g);\r
418     font = new Font("Verdana", Font.PLAIN, fontSize);\r
419     g.setFont(font);\r
420 \r
421     FontMetrics fm = g.getFontMetrics(font);\r
422 \r
423     if (nameHash.size() == 0)\r
424     {\r
425       repaint();\r
426     }\r
427 \r
428     if (fitToWindow ||\r
429         (!fitToWindow &&\r
430          (scrollPane.getHeight() > ( (fm.getHeight() * nameHash.size()) +\r
431                                     offy))))\r
432     {\r
433       draw(g, scrollPane.getWidth(), scrollPane.getHeight());\r
434       setPreferredSize(null);\r
435     }\r
436     else\r
437     {\r
438       setPreferredSize(new Dimension(scrollPane.getWidth(),\r
439                                      fm.getHeight() * nameHash.size()));\r
440       draw(g, scrollPane.getWidth(), fm.getHeight() * nameHash.size());\r
441     }\r
442 \r
443     scrollPane.revalidate();\r
444   }\r
445 \r
446   public int getFontSize()\r
447   {\r
448     return fontSize;\r
449   }\r
450 \r
451   public void setFontSize(int fontSize)\r
452   {\r
453     this.fontSize = fontSize;\r
454     repaint();\r
455   }\r
456 \r
457   public void draw(Graphics g1, int width, int height)\r
458   {\r
459     Graphics2D g2 = (Graphics2D) g1;\r
460     g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,\r
461                         RenderingHints.VALUE_ANTIALIAS_ON);\r
462     g2.setColor(Color.white);\r
463     g2.fillRect(0, 0, width, height);\r
464 \r
465     labelLength = g2.getFontMetrics(font).stringWidth(longestName) + 20; //20 allows for scrollbar\r
466 \r
467     float wscale = (float) (width - labelLength - (offx * 2)) /\r
468         tree.getMaxHeight();\r
469 \r
470     SequenceNode top = tree.getTopNode();\r
471 \r
472     if (top.count == 0)\r
473     {\r
474       top.count = ( (SequenceNode) top.left()).count +\r
475           ( (SequenceNode) top.right()).count;\r
476     }\r
477 \r
478     float chunk = (float) (height - (offy * 2)) / top.count;\r
479 \r
480     drawNode(g2, tree.getTopNode(), chunk, wscale, width, offx, offy);\r
481 \r
482     if (threshold != 0)\r
483     {\r
484       if (av.getCurrentTree() == tree)\r
485       {\r
486         g2.setColor(Color.red);\r
487       }\r
488       else\r
489       {\r
490         g2.setColor(Color.gray);\r
491       }\r
492 \r
493       int x = (int) ( (threshold * (float) (getWidth() - labelLength -\r
494                                             (2 * offx))) + offx);\r
495 \r
496       g2.drawLine(x, 0, x, getHeight());\r
497     }\r
498   }\r
499 \r
500   public void mouseReleased(MouseEvent e)\r
501   {\r
502   }\r
503 \r
504   public void mouseEntered(MouseEvent e)\r
505   {\r
506   }\r
507 \r
508   public void mouseExited(MouseEvent e)\r
509   {\r
510   }\r
511 \r
512   public void mouseClicked(MouseEvent e)\r
513   {\r
514   }\r
515 \r
516   public void mousePressed(MouseEvent e)\r
517   {\r
518     av.setCurrentTree(tree);\r
519 \r
520     int x = e.getX();\r
521     int y = e.getY();\r
522 \r
523     Object ob = findElement(x, y);\r
524 \r
525     if (ob instanceof SequenceI)\r
526     {\r
527       TreeSelectionChanged( (Sequence) ob);\r
528       repaint();\r
529 \r
530       return;\r
531     }\r
532     else if (ob instanceof SequenceNode)\r
533     {\r
534       SequenceNode tmpnode = (SequenceNode) ob;\r
535       tree.swapNodes(tmpnode);\r
536       tree.reCount(tree.getTopNode());\r
537       tree.findHeight(tree.getTopNode());\r
538     }\r
539     else\r
540     {\r
541       // Find threshold\r
542       if (tree.getMaxHeight() != 0)\r
543       {\r
544         threshold = (float) (x - offx) / (float) (getWidth() -\r
545                                                   labelLength - (2 * offx));\r
546 \r
547         tree.getGroups().removeAllElements();\r
548         tree.groupNodes(tree.getTopNode(), threshold);\r
549         setColor(tree.getTopNode(), Color.black);\r
550 \r
551         av.setSelectionGroup(null);\r
552         av.alignment.deleteAllGroups();\r
553 \r
554         for (int i = 0; i < tree.getGroups().size(); i++)\r
555         {\r
556           Color col = new Color( (int) (Math.random() * 255),\r
557                                 (int) (Math.random() * 255),\r
558                                 (int) (Math.random() * 255));\r
559           setColor( (SequenceNode) tree.getGroups().elementAt(i),\r
560                    col.brighter());\r
561 \r
562           Vector l = tree.findLeaves( (SequenceNode) tree.getGroups()\r
563                                      .elementAt(i),\r
564                                      new Vector());\r
565 \r
566          Vector sequences = new Vector();\r
567          for (int j = 0; j < l.size(); j++)\r
568          {\r
569            SequenceI s1 = (SequenceI) ( (SequenceNode) l.elementAt(j)).element();\r
570            if (!sequences.contains(s1))\r
571              sequences.addElement(s1);\r
572 \r
573           }\r
574 \r
575           ColourSchemeI cs = ColourSchemeProperty.getColour(sequences, av.alignment.getWidth(),\r
576               ColourSchemeProperty.getColourName(av.getGlobalColourScheme()));\r
577 \r
578           SequenceGroup sg = new SequenceGroup(sequences, "TreeGroup",\r
579                            cs, true, true,\r
580                            false, 0, av.alignment.getWidth());\r
581 \r
582           if(sg.cs !=null)\r
583            ( (ResidueColourScheme) sg.cs).setThreshold(25);\r
584 \r
585 \r
586           if (av.getGlobalColourScheme() instanceof ConservationColourScheme)\r
587           {\r
588             ConservationColourScheme ccs = (ConservationColourScheme) av.\r
589                 getGlobalColourScheme();\r
590             Conservation c = new Conservation("Group",\r
591                                               ResidueProperties.propHash, 3,\r
592                                               sg.sequences,\r
593                                               sg.getStartRes(), sg.getEndRes());\r
594 \r
595             c.calculate();\r
596             c.verdict(false, av.ConsPercGaps);\r
597             ccs = new ConservationColourScheme(c, ccs.cs);\r
598 \r
599             sg.cs = ccs;\r
600           }\r
601 \r
602           av.alignment.addGroup(sg);\r
603         }\r
604       }\r
605     }\r
606 \r
607     PaintRefresher.Refresh(this, av.alignment);\r
608     repaint();\r
609   }\r
610 \r
611   public void setShowDistances(boolean state)\r
612   {\r
613     this.showDistances = state;\r
614     repaint();\r
615   }\r
616 \r
617   public void setShowBootstrap(boolean state)\r
618   {\r
619     this.showBootstrap = state;\r
620     repaint();\r
621   }\r
622 \r
623   public void setMarkPlaceholders(boolean state)\r
624   {\r
625     this.markPlaceholders = state;\r
626     repaint();\r
627   }\r
628 }\r