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