eef07fa999254bfb99c0ef2d40a4edc80ffc5f62
[jalview.git] / src / jalview / gui / TreePanel.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.4)
3  * Copyright (C) 2008 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle
4  * 
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  * 
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  * 
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
18  */
19 package jalview.gui;
20
21 import java.beans.*;
22 import java.io.*;
23 import java.util.*;
24 import javax.imageio.*;
25
26 import java.awt.*;
27 import java.awt.event.*;
28 import java.awt.image.*;
29 import javax.swing.*;
30
31 import org.jibble.epsgraphics.*;
32 import jalview.analysis.*;
33 import jalview.commands.OrderCommand;
34 import jalview.datamodel.*;
35 import jalview.io.*;
36 import jalview.jbgui.*;
37
38 /**
39  * DOCUMENT ME!
40  * 
41  * @author $author$
42  * @version $Revision$
43  */
44 public class TreePanel extends GTreePanel
45 {
46   String type;
47
48   String pwtype;
49
50   TreeCanvas treeCanvas;
51
52   NJTree tree;
53
54   AlignViewport av;
55
56   /**
57    * Creates a new TreePanel object.
58    * 
59    * @param av
60    *          DOCUMENT ME!
61    * @param seqVector
62    *          DOCUMENT ME!
63    * @param type
64    *          DOCUMENT ME!
65    * @param pwtype
66    *          DOCUMENT ME!
67    * @param s
68    *          DOCUMENT ME!
69    * @param e
70    *          DOCUMENT ME!
71    */
72   public TreePanel(AlignmentPanel ap, String type, String pwtype)
73   {
74     super();
75     initTreePanel(ap, type, pwtype, null, null);
76
77     // We know this tree has distances. JBPNote TODO: prolly should add this as
78     // a userdefined default
79     // showDistances(true);
80   }
81
82   /**
83    * Creates a new TreePanel object.
84    * 
85    * @param av
86    *          DOCUMENT ME!
87    * @param seqVector
88    *          DOCUMENT ME!
89    * @param newtree
90    *          DOCUMENT ME!
91    * @param type
92    *          DOCUMENT ME!
93    * @param pwtype
94    *          DOCUMENT ME!
95    */
96   public TreePanel(AlignmentPanel ap, String type, String pwtype,
97           NewickFile newtree)
98   {
99     super();
100     initTreePanel(ap, type, pwtype, newtree, null);
101   }
102
103   public TreePanel(AlignmentPanel av, String type, String pwtype,
104           NewickFile newtree, AlignmentView inputData)
105   {
106     super();
107     initTreePanel(av, type, pwtype, newtree, inputData);
108   }
109
110   public AlignmentI getAlignment()
111   {
112     return treeCanvas.av.getAlignment();
113   }
114
115   public AlignViewport getViewPort()
116   {
117     return treeCanvas.av;
118   }
119
120   void initTreePanel(AlignmentPanel ap, String type, String pwtype,
121           NewickFile newTree, AlignmentView inputData)
122   {
123
124     av = ap.av;
125     this.type = type;
126     this.pwtype = pwtype;
127
128     treeCanvas = new TreeCanvas(this, ap, scrollPane);
129     scrollPane.setViewportView(treeCanvas);
130
131     PaintRefresher.Register(this, ap.av.getSequenceSetId());
132
133     buildAssociatedViewMenu();
134
135     av.addPropertyChangeListener(new java.beans.PropertyChangeListener()
136     {
137       public void propertyChange(PropertyChangeEvent evt)
138       {
139         if (evt.getPropertyName().equals("alignment"))
140         {
141           if (tree == null)
142           {
143             System.out.println("tree is null");
144           }
145           if (evt.getNewValue() == null)
146           {
147             System.out
148                     .println("new alignment sequences vector value is null");
149           }
150
151           tree.UpdatePlaceHolders((Vector) evt.getNewValue());
152           treeCanvas.nameHash.clear(); // reset the mapping between canvas
153           // rectangles and leafnodes
154           repaint();
155         }
156       }
157     });
158
159     TreeLoader tl = new TreeLoader(newTree);
160     if (inputData != null)
161     {
162       tl.odata = inputData;
163     }
164     tl.start();
165
166   }
167
168   public void viewMenu_menuSelected()
169   {
170     buildAssociatedViewMenu();
171   }
172
173   void buildAssociatedViewMenu()
174   {
175     AlignmentPanel[] aps = PaintRefresher.getAssociatedPanels(av
176             .getSequenceSetId());
177     if (aps.length == 1 && treeCanvas.ap == aps[0])
178     {
179       associateLeavesMenu.setVisible(false);
180       return;
181     }
182
183     associateLeavesMenu.setVisible(true);
184
185     if ((viewMenu.getItem(viewMenu.getItemCount() - 2) instanceof JMenuItem))
186     {
187       viewMenu.insertSeparator(viewMenu.getItemCount() - 1);
188     }
189
190     associateLeavesMenu.removeAll();
191
192     JRadioButtonMenuItem item;
193     ButtonGroup buttonGroup = new ButtonGroup();
194     int i, iSize = aps.length;
195     final TreePanel thisTreePanel = this;
196     for (i = 0; i < iSize; i++)
197     {
198       final AlignmentPanel ap = aps[i];
199       item = new JRadioButtonMenuItem(ap.av.viewName, ap == treeCanvas.ap);
200       buttonGroup.add(item);
201       item.addActionListener(new ActionListener()
202       {
203         public void actionPerformed(ActionEvent evt)
204         {
205           treeCanvas.applyToAllViews = false;
206           treeCanvas.ap = ap;
207           treeCanvas.av = ap.av;
208           PaintRefresher.Register(thisTreePanel, ap.av.getSequenceSetId());
209         }
210       });
211
212       associateLeavesMenu.add(item);
213     }
214
215     final JRadioButtonMenuItem itemf = new JRadioButtonMenuItem("All Views");
216     buttonGroup.add(itemf);
217     itemf.setSelected(treeCanvas.applyToAllViews);
218     itemf.addActionListener(new ActionListener()
219     {
220       public void actionPerformed(ActionEvent evt)
221       {
222         treeCanvas.applyToAllViews = itemf.isSelected();
223       }
224     });
225     associateLeavesMenu.add(itemf);
226
227   }
228
229   class TreeLoader extends Thread
230   {
231     NewickFile newtree;
232
233     jalview.datamodel.AlignmentView odata = null;
234
235     public TreeLoader(NewickFile newtree)
236     {
237       this.newtree = newtree;
238       if (newtree != null)
239       {
240         // Must be outside run(), as Jalview2XML tries to
241         // update distance/bootstrap visibility at the same time
242         showBootstrap(newtree.HasBootstrap());
243         showDistances(newtree.HasDistances());
244       }
245     }
246
247     public void run()
248     {
249
250       if (newtree != null)
251       {
252         if (odata == null)
253         {
254           tree = new NJTree(av.alignment.getSequencesArray(), newtree);
255         }
256         else
257         {
258           tree = new NJTree(av.alignment.getSequencesArray(), odata,
259                   newtree);
260         }
261         if (!tree.hasOriginalSequenceData())
262         {
263           allowOriginalSeqData(false);
264         }
265       }
266       else
267       {
268         int start, end;
269         SequenceI[] seqs;
270         AlignmentView seqStrings = av.getAlignmentView(av
271                 .getSelectionGroup() != null);
272         if (av.getSelectionGroup() == null)
273         {
274           start = 0;
275           end = av.alignment.getWidth();
276           seqs = av.alignment.getSequencesArray();
277         }
278         else
279         {
280           start = av.getSelectionGroup().getStartRes();
281           end = av.getSelectionGroup().getEndRes() + 1;
282           seqs = av.getSelectionGroup().getSequencesInOrder(av.alignment);
283         }
284
285         tree = new NJTree(seqs, seqStrings, type, pwtype, start, end);
286         showDistances(true);
287       }
288
289       tree.reCount(tree.getTopNode());
290       tree.findHeight(tree.getTopNode());
291       treeCanvas.setTree(tree);
292       treeCanvas.repaint();
293       av.setCurrentTree(tree);
294       if (av.getSortByTree())
295       {
296         sortByTree_actionPerformed(null);
297       }
298     }
299   }
300
301   public void showDistances(boolean b)
302   {
303     treeCanvas.setShowDistances(b);
304     distanceMenu.setSelected(b);
305   }
306
307   public void showBootstrap(boolean b)
308   {
309     treeCanvas.setShowBootstrap(b);
310     bootstrapMenu.setSelected(b);
311   }
312
313   public void showPlaceholders(boolean b)
314   {
315     placeholdersMenu.setState(b);
316     treeCanvas.setMarkPlaceholders(b);
317   }
318
319   private void allowOriginalSeqData(boolean b)
320   {
321     originalSeqData.setVisible(b);
322   }
323
324   /**
325    * DOCUMENT ME!
326    * 
327    * @return DOCUMENT ME!
328    */
329   public NJTree getTree()
330   {
331     return tree;
332   }
333
334   /**
335    * DOCUMENT ME!
336    * 
337    * @param e
338    *          DOCUMENT ME!
339    */
340   public void textbox_actionPerformed(ActionEvent e)
341   {
342     CutAndPasteTransfer cap = new CutAndPasteTransfer();
343
344     StringBuffer buffer = new StringBuffer();
345
346     if (type.equals("AV"))
347     {
348       buffer.append("Average distance tree using ");
349     }
350     else
351     {
352       buffer.append("Neighbour joining tree using ");
353     }
354
355     if (pwtype.equals("BL"))
356     {
357       buffer.append("BLOSUM62");
358     }
359     else
360     {
361       buffer.append("PID");
362     }
363
364     Desktop.addInternalFrame(cap, buffer.toString(), 500, 100);
365
366     jalview.io.NewickFile fout = new jalview.io.NewickFile(tree
367             .getTopNode());
368     cap.setText(fout.print(tree.isHasBootstrap(), tree.isHasDistances(),
369             tree.isHasRootDistance()));
370   }
371
372   /**
373    * DOCUMENT ME!
374    * 
375    * @param e
376    *          DOCUMENT ME!
377    */
378   public void saveAsNewick_actionPerformed(ActionEvent e)
379   {
380     JalviewFileChooser chooser = new JalviewFileChooser(jalview.bin.Cache
381             .getProperty("LAST_DIRECTORY"));
382     chooser.setFileView(new JalviewFileView());
383     chooser.setDialogTitle("Save tree as newick file");
384     chooser.setToolTipText("Save");
385
386     int value = chooser.showSaveDialog(null);
387
388     if (value == JalviewFileChooser.APPROVE_OPTION)
389     {
390       String choice = chooser.getSelectedFile().getPath();
391       jalview.bin.Cache.setProperty("LAST_DIRECTORY", chooser
392               .getSelectedFile().getParent());
393
394       try
395       {
396         jalview.io.NewickFile fout = new jalview.io.NewickFile(tree
397                 .getTopNode());
398         String output = fout.print(tree.isHasBootstrap(), tree
399                 .isHasDistances(), tree.isHasRootDistance());
400         java.io.PrintWriter out = new java.io.PrintWriter(
401                 new java.io.FileWriter(choice));
402         out.println(output);
403         out.close();
404       } catch (Exception ex)
405       {
406         ex.printStackTrace();
407       }
408     }
409   }
410
411   /**
412    * DOCUMENT ME!
413    * 
414    * @param e
415    *          DOCUMENT ME!
416    */
417   public void printMenu_actionPerformed(ActionEvent e)
418   {
419     // Putting in a thread avoids Swing painting problems
420     treeCanvas.startPrinting();
421   }
422
423   public void originalSeqData_actionPerformed(ActionEvent e)
424   {
425     if (!tree.hasOriginalSequenceData())
426     {
427       jalview.bin.Cache.log
428               .info("Unexpected call to originalSeqData_actionPerformed - should have hidden this menu action.");
429       return;
430     }
431     // decide if av alignment is sufficiently different to original data to
432     // warrant a new window to be created
433     // create new alignmnt window with hidden regions (unhiding hidden regions
434     // yields unaligned seqs)
435     // or create a selection box around columns in alignment view
436     // test Alignment(SeqCigar[])
437     char gc = '-';
438     try
439     {
440       // we try to get the associated view's gap character
441       // but this may fail if the view was closed...
442       gc = av.getGapCharacter();
443
444     } catch (Exception ex)
445     {
446     }
447     ;
448     Object[] alAndColsel = tree.seqData.getAlignmentAndColumnSelection(gc);
449
450     if (alAndColsel != null && alAndColsel[0] != null)
451     {
452       // AlignmentOrder origorder = new AlignmentOrder(alAndColsel[0]);
453
454       Alignment al = new Alignment((SequenceI[]) alAndColsel[0]);
455       Alignment dataset = (av != null && av.getAlignment() != null) ? av
456               .getAlignment().getDataset() : null;
457       if (dataset != null)
458       {
459         al.setDataset(dataset);
460       }
461
462       if (true)
463       {
464         // make a new frame!
465         AlignFrame af = new AlignFrame(al,
466                 (ColumnSelection) alAndColsel[1], AlignFrame.DEFAULT_WIDTH,
467                 AlignFrame.DEFAULT_HEIGHT);
468
469         // >>>This is a fix for the moment, until a better solution is
470         // found!!<<<
471         // af.getFeatureRenderer().transferSettings(alignFrame.getFeatureRenderer());
472
473         // af.addSortByOrderMenuItem(ServiceName + " Ordering",
474         // msaorder);
475
476         Desktop.addInternalFrame(af, "Original Data for " + this.title,
477                 AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
478       }
479     }
480   }
481
482   /**
483    * DOCUMENT ME!
484    * 
485    * @param e
486    *          DOCUMENT ME!
487    */
488   public void fitToWindow_actionPerformed(ActionEvent e)
489   {
490     treeCanvas.fitToWindow = fitToWindow.isSelected();
491     repaint();
492   }
493
494   /**
495    * sort the associated alignment view by the current tree.
496    * @param e
497    */
498   public void sortByTree_actionPerformed(ActionEvent e)
499   {
500     AlignmentPanel ap = av.getAlignPanel();
501     SequenceI[] oldOrder = av.getAlignment().getSequencesArray();
502     AlignmentSorter.sortByTree(av.getAlignment(), tree);
503
504     ap.alignFrame
505             .addHistoryItem(new OrderCommand("Tree Sort", oldOrder,
506                     av.alignment));
507
508     ap.paintAlignment(true);
509
510   }
511
512   /**
513    * DOCUMENT ME!
514    * 
515    * @param e
516    *          DOCUMENT ME!
517    */
518   public void font_actionPerformed(ActionEvent e)
519   {
520     if (treeCanvas == null)
521     {
522       return;
523     }
524
525     new FontChooser(this);
526   }
527
528   public Font getTreeFont()
529   {
530     return treeCanvas.font;
531   }
532
533   public void setTreeFont(Font font)
534   {
535     if (treeCanvas != null)
536     {
537       treeCanvas.setFont(font);
538     }
539   }
540
541   /**
542    * DOCUMENT ME!
543    * 
544    * @param e
545    *          DOCUMENT ME!
546    */
547   public void distanceMenu_actionPerformed(ActionEvent e)
548   {
549     treeCanvas.setShowDistances(distanceMenu.isSelected());
550   }
551
552   /**
553    * DOCUMENT ME!
554    * 
555    * @param e
556    *          DOCUMENT ME!
557    */
558   public void bootstrapMenu_actionPerformed(ActionEvent e)
559   {
560     treeCanvas.setShowBootstrap(bootstrapMenu.isSelected());
561   }
562
563   /**
564    * DOCUMENT ME!
565    * 
566    * @param e
567    *          DOCUMENT ME!
568    */
569   public void placeholdersMenu_actionPerformed(ActionEvent e)
570   {
571     treeCanvas.setMarkPlaceholders(placeholdersMenu.isSelected());
572   }
573
574   /**
575    * DOCUMENT ME!
576    * 
577    * @param e
578    *          DOCUMENT ME!
579    */
580   public void epsTree_actionPerformed(ActionEvent e)
581   {
582     boolean accurateText = true;
583
584     String renderStyle = jalview.bin.Cache.getDefault("EPS_RENDERING",
585             "Prompt each time");
586
587     // If we need to prompt, and if the GUI is visible then
588     // Prompt for EPS rendering style
589     if (renderStyle.equalsIgnoreCase("Prompt each time")
590             && !(System.getProperty("java.awt.headless") != null && System
591                     .getProperty("java.awt.headless").equals("true")))
592     {
593       EPSOptions eps = new EPSOptions();
594       renderStyle = eps.getValue();
595
596       if (renderStyle == null || eps.cancelled)
597       {
598         return;
599       }
600
601     }
602
603     if (renderStyle.equalsIgnoreCase("text"))
604     {
605       accurateText = false;
606     }
607
608     int width = treeCanvas.getWidth();
609     int height = treeCanvas.getHeight();
610
611     try
612     {
613       jalview.io.JalviewFileChooser chooser = new jalview.io.JalviewFileChooser(
614               jalview.bin.Cache.getProperty("LAST_DIRECTORY"), new String[]
615               { "eps" }, new String[]
616               { "Encapsulated Postscript" }, "Encapsulated Postscript");
617       chooser.setFileView(new jalview.io.JalviewFileView());
618       chooser.setDialogTitle("Create EPS file from tree");
619       chooser.setToolTipText("Save");
620
621       int value = chooser.showSaveDialog(this);
622
623       if (value != jalview.io.JalviewFileChooser.APPROVE_OPTION)
624       {
625         return;
626       }
627
628       jalview.bin.Cache.setProperty("LAST_DIRECTORY", chooser
629               .getSelectedFile().getParent());
630
631       FileOutputStream out = new FileOutputStream(chooser.getSelectedFile());
632       EpsGraphics2D pg = new EpsGraphics2D("Tree", out, 0, 0, width, height);
633
634       pg.setAccurateTextMode(accurateText);
635
636       treeCanvas.draw(pg, width, height);
637
638       pg.flush();
639       pg.close();
640     } catch (Exception ex)
641     {
642       ex.printStackTrace();
643     }
644   }
645
646   /**
647    * DOCUMENT ME!
648    * 
649    * @param e
650    *          DOCUMENT ME!
651    */
652   public void pngTree_actionPerformed(ActionEvent e)
653   {
654     int width = treeCanvas.getWidth();
655     int height = treeCanvas.getHeight();
656
657     try
658     {
659       jalview.io.JalviewFileChooser chooser = new jalview.io.JalviewFileChooser(
660               jalview.bin.Cache.getProperty("LAST_DIRECTORY"), new String[]
661               { "png" }, new String[]
662               { "Portable network graphics" }, "Portable network graphics");
663
664       chooser.setFileView(new jalview.io.JalviewFileView());
665       chooser.setDialogTitle("Create PNG image from tree");
666       chooser.setToolTipText("Save");
667
668       int value = chooser.showSaveDialog(this);
669
670       if (value != jalview.io.JalviewFileChooser.APPROVE_OPTION)
671       {
672         return;
673       }
674
675       jalview.bin.Cache.setProperty("LAST_DIRECTORY", chooser
676               .getSelectedFile().getParent());
677
678       FileOutputStream out = new FileOutputStream(chooser.getSelectedFile());
679
680       BufferedImage bi = new BufferedImage(width, height,
681               BufferedImage.TYPE_INT_RGB);
682       Graphics png = bi.getGraphics();
683
684       treeCanvas.draw(png, width, height);
685
686       ImageIO.write(bi, "png", out);
687       out.close();
688     } catch (Exception ex)
689     {
690       ex.printStackTrace();
691     }
692   }
693   /**
694    * change node labels to the annotation referred to by labelClass
695    * TODO: promote to a datamodel modification that can be undone
696    * TODO: make argument one case of a generic transformation function ie { undoStep = apply(Tree, TransformFunction)};
697    * @param labelClass
698    */
699   public void changeNames(final String labelClass)
700   {
701     tree.applyToNodes(new NodeTransformI() {
702
703       public void transform(BinaryNode node)
704       {
705         if (node instanceof SequenceNode && !((SequenceNode) node).isPlaceholder() && !((SequenceNode) node).isDummy())
706         {
707           String newname=null;
708           SequenceI sq = (SequenceI) ((SequenceNode) node).element();
709           if (sq!=null)
710           {
711             // search dbrefs, features and annotation
712             DBRefEntry[] refs = jalview.util.DBRefUtils.selectRefs(sq.getDBRef(), new String[] {labelClass.toUpperCase()});
713             if (refs!=null)
714             {
715               for (int i=0; i<refs.length;i++)
716               {
717                 if (newname == null) {
718                     newname = new String(refs[i].getAccessionId());
719                 } else {
720                   newname = newname +"; "+refs[i].getAccessionId();
721                 }
722               }
723             }
724             if (newname==null)
725             {
726               SequenceFeature sf[] = sq.getSequenceFeatures();
727               for (int i=0;sf!=null && i<sf.length; i++)
728               {
729                 if (sf[i].getType().equals(labelClass))
730                 {
731                   if (newname==null)
732                   {
733                     newname = new String(sf[i].getDescription());
734                   } else {
735                     newname = newname + "; "+sf[i].getDescription();
736                   }
737                 }
738               }
739             }
740         }
741           if (newname!=null)
742           {
743             String oldname = ((SequenceNode) node).getName();
744             // TODO : save in the undo object for this modification.
745             ((SequenceNode) node).setName(newname);
746           }
747         }
748       }
749     });
750   }
751 }