45fc0df055332438c83276ef852bc688e13fbcb1
[jalview.git] / src / jalview / gui / TreePanel.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.5)
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     Desktop.addInternalFrame(cap, buffer.toString(), 500, 100);
368
369     jalview.io.NewickFile fout = new jalview.io.NewickFile(tree
370             .getTopNode());
371     cap.setText(fout.print(tree.isHasBootstrap(), tree.isHasDistances(),
372             tree.isHasRootDistance()));
373   }
374
375   /**
376    * DOCUMENT ME!
377    * 
378    * @param e
379    *          DOCUMENT ME!
380    */
381   public void saveAsNewick_actionPerformed(ActionEvent e)
382   {
383     JalviewFileChooser chooser = new JalviewFileChooser(jalview.bin.Cache
384             .getProperty("LAST_DIRECTORY"));
385     chooser.setFileView(new JalviewFileView());
386     chooser.setDialogTitle("Save tree as newick file");
387     chooser.setToolTipText("Save");
388
389     int value = chooser.showSaveDialog(null);
390
391     if (value == JalviewFileChooser.APPROVE_OPTION)
392     {
393       String choice = chooser.getSelectedFile().getPath();
394       jalview.bin.Cache.setProperty("LAST_DIRECTORY", chooser
395               .getSelectedFile().getParent());
396
397       try
398       {
399         jalview.io.NewickFile fout = new jalview.io.NewickFile(tree
400                 .getTopNode());
401         String output = fout.print(tree.isHasBootstrap(), tree
402                 .isHasDistances(), tree.isHasRootDistance());
403         java.io.PrintWriter out = new java.io.PrintWriter(
404                 new java.io.FileWriter(choice));
405         out.println(output);
406         out.close();
407       } catch (Exception ex)
408       {
409         ex.printStackTrace();
410       }
411     }
412   }
413
414   /**
415    * DOCUMENT ME!
416    * 
417    * @param e
418    *          DOCUMENT ME!
419    */
420   public void printMenu_actionPerformed(ActionEvent e)
421   {
422     // Putting in a thread avoids Swing painting problems
423     treeCanvas.startPrinting();
424   }
425
426   public void originalSeqData_actionPerformed(ActionEvent e)
427   {
428     if (!tree.hasOriginalSequenceData())
429     {
430       jalview.bin.Cache.log
431               .info("Unexpected call to originalSeqData_actionPerformed - should have hidden this menu action.");
432       return;
433     }
434     // decide if av alignment is sufficiently different to original data to
435     // warrant a new window to be created
436     // create new alignmnt window with hidden regions (unhiding hidden regions
437     // yields unaligned seqs)
438     // or create a selection box around columns in alignment view
439     // test Alignment(SeqCigar[])
440     char gc = '-';
441     try
442     {
443       // we try to get the associated view's gap character
444       // but this may fail if the view was closed...
445       gc = av.getGapCharacter();
446
447     } catch (Exception ex)
448     {
449     }
450     ;
451     Object[] alAndColsel = tree.seqData.getAlignmentAndColumnSelection(gc);
452
453     if (alAndColsel != null && alAndColsel[0] != null)
454     {
455       // AlignmentOrder origorder = new AlignmentOrder(alAndColsel[0]);
456
457       Alignment al = new Alignment((SequenceI[]) alAndColsel[0]);
458       Alignment dataset = (av != null && av.getAlignment() != null) ? av
459               .getAlignment().getDataset() : null;
460       if (dataset != null)
461       {
462         al.setDataset(dataset);
463       }
464
465       if (true)
466       {
467         // make a new frame!
468         AlignFrame af = new AlignFrame(al,
469                 (ColumnSelection) alAndColsel[1], AlignFrame.DEFAULT_WIDTH,
470                 AlignFrame.DEFAULT_HEIGHT);
471
472         // >>>This is a fix for the moment, until a better solution is
473         // found!!<<<
474         // af.getFeatureRenderer().transferSettings(alignFrame.getFeatureRenderer());
475
476         // af.addSortByOrderMenuItem(ServiceName + " Ordering",
477         // msaorder);
478
479         Desktop.addInternalFrame(af, "Original Data for " + this.title,
480                 AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
481       }
482     }
483   }
484
485   /**
486    * DOCUMENT ME!
487    * 
488    * @param e
489    *          DOCUMENT ME!
490    */
491   public void fitToWindow_actionPerformed(ActionEvent e)
492   {
493     treeCanvas.fitToWindow = fitToWindow.isSelected();
494     repaint();
495   }
496
497   /**
498    * sort the associated alignment view by the current tree.
499    * 
500    * @param e
501    */
502   public void sortByTree_actionPerformed(ActionEvent e)
503   {
504     AlignmentPanel ap = av.getAlignPanel();
505     SequenceI[] oldOrder = av.getAlignment().getSequencesArray();
506     AlignmentSorter.sortByTree(av.getAlignment(), tree);
507
508     ap.alignFrame.addHistoryItem(new OrderCommand("Tree Sort", oldOrder,
509             av.alignment));
510
511     ap.paintAlignment(true);
512
513   }
514
515   /**
516    * DOCUMENT ME!
517    * 
518    * @param e
519    *          DOCUMENT ME!
520    */
521   public void font_actionPerformed(ActionEvent e)
522   {
523     if (treeCanvas == null)
524     {
525       return;
526     }
527
528     new FontChooser(this);
529   }
530
531   public Font getTreeFont()
532   {
533     return treeCanvas.font;
534   }
535
536   public void setTreeFont(Font font)
537   {
538     if (treeCanvas != null)
539     {
540       treeCanvas.setFont(font);
541     }
542   }
543
544   /**
545    * DOCUMENT ME!
546    * 
547    * @param e
548    *          DOCUMENT ME!
549    */
550   public void distanceMenu_actionPerformed(ActionEvent e)
551   {
552     treeCanvas.setShowDistances(distanceMenu.isSelected());
553   }
554
555   /**
556    * DOCUMENT ME!
557    * 
558    * @param e
559    *          DOCUMENT ME!
560    */
561   public void bootstrapMenu_actionPerformed(ActionEvent e)
562   {
563     treeCanvas.setShowBootstrap(bootstrapMenu.isSelected());
564   }
565
566   /**
567    * DOCUMENT ME!
568    * 
569    * @param e
570    *          DOCUMENT ME!
571    */
572   public void placeholdersMenu_actionPerformed(ActionEvent e)
573   {
574     treeCanvas.setMarkPlaceholders(placeholdersMenu.isSelected());
575   }
576
577   /**
578    * DOCUMENT ME!
579    * 
580    * @param e
581    *          DOCUMENT ME!
582    */
583   public void epsTree_actionPerformed(ActionEvent e)
584   {
585     boolean accurateText = true;
586
587     String renderStyle = jalview.bin.Cache.getDefault("EPS_RENDERING",
588             "Prompt each time");
589
590     // If we need to prompt, and if the GUI is visible then
591     // Prompt for EPS rendering style
592     if (renderStyle.equalsIgnoreCase("Prompt each time")
593             && !(System.getProperty("java.awt.headless") != null && System
594                     .getProperty("java.awt.headless").equals("true")))
595     {
596       EPSOptions eps = new EPSOptions();
597       renderStyle = eps.getValue();
598
599       if (renderStyle == null || eps.cancelled)
600       {
601         return;
602       }
603
604     }
605
606     if (renderStyle.equalsIgnoreCase("text"))
607     {
608       accurateText = false;
609     }
610
611     int width = treeCanvas.getWidth();
612     int height = treeCanvas.getHeight();
613
614     try
615     {
616       jalview.io.JalviewFileChooser chooser = new jalview.io.JalviewFileChooser(
617               jalview.bin.Cache.getProperty("LAST_DIRECTORY"), new String[]
618               { "eps" }, new String[]
619               { "Encapsulated Postscript" }, "Encapsulated Postscript");
620       chooser.setFileView(new jalview.io.JalviewFileView());
621       chooser.setDialogTitle("Create EPS file from tree");
622       chooser.setToolTipText("Save");
623
624       int value = chooser.showSaveDialog(this);
625
626       if (value != jalview.io.JalviewFileChooser.APPROVE_OPTION)
627       {
628         return;
629       }
630
631       jalview.bin.Cache.setProperty("LAST_DIRECTORY", chooser
632               .getSelectedFile().getParent());
633
634       FileOutputStream out = new FileOutputStream(chooser.getSelectedFile());
635       EpsGraphics2D pg = new EpsGraphics2D("Tree", out, 0, 0, width, height);
636
637       pg.setAccurateTextMode(accurateText);
638
639       treeCanvas.draw(pg, width, height);
640
641       pg.flush();
642       pg.close();
643     } catch (Exception ex)
644     {
645       ex.printStackTrace();
646     }
647   }
648
649   /**
650    * DOCUMENT ME!
651    * 
652    * @param e
653    *          DOCUMENT ME!
654    */
655   public void pngTree_actionPerformed(ActionEvent e)
656   {
657     int width = treeCanvas.getWidth();
658     int height = treeCanvas.getHeight();
659
660     try
661     {
662       jalview.io.JalviewFileChooser chooser = new jalview.io.JalviewFileChooser(
663               jalview.bin.Cache.getProperty("LAST_DIRECTORY"), new String[]
664               { "png" }, new String[]
665               { "Portable network graphics" }, "Portable network graphics");
666
667       chooser.setFileView(new jalview.io.JalviewFileView());
668       chooser.setDialogTitle("Create PNG image from tree");
669       chooser.setToolTipText("Save");
670
671       int value = chooser.showSaveDialog(this);
672
673       if (value != jalview.io.JalviewFileChooser.APPROVE_OPTION)
674       {
675         return;
676       }
677
678       jalview.bin.Cache.setProperty("LAST_DIRECTORY", chooser
679               .getSelectedFile().getParent());
680
681       FileOutputStream out = new FileOutputStream(chooser.getSelectedFile());
682
683       BufferedImage bi = new BufferedImage(width, height,
684               BufferedImage.TYPE_INT_RGB);
685       Graphics png = bi.getGraphics();
686
687       treeCanvas.draw(png, width, height);
688
689       ImageIO.write(bi, "png", out);
690       out.close();
691     } catch (Exception ex)
692     {
693       ex.printStackTrace();
694     }
695   }
696
697   /**
698    * change node labels to the annotation referred to by labelClass TODO:
699    * promote to a datamodel modification that can be undone TODO: make argument
700    * one case of a generic transformation function ie { undoStep = apply(Tree,
701    * TransformFunction)};
702    * 
703    * @param labelClass
704    */
705   public void changeNames(final String labelClass)
706   {
707     tree.applyToNodes(new NodeTransformI()
708     {
709
710       public void transform(BinaryNode node)
711       {
712         if (node instanceof SequenceNode
713                 && !((SequenceNode) node).isPlaceholder()
714                 && !((SequenceNode) node).isDummy())
715         {
716           String newname = null;
717           SequenceI sq = (SequenceI) ((SequenceNode) node).element();
718           if (sq != null)
719           {
720             // search dbrefs, features and annotation
721             DBRefEntry[] refs = jalview.util.DBRefUtils.selectRefs(sq
722                     .getDBRef(), new String[]
723             { labelClass.toUpperCase() });
724             if (refs != null)
725             {
726               for (int i = 0; i < refs.length; i++)
727               {
728                 if (newname == null)
729                 {
730                   newname = new String(refs[i].getAccessionId());
731                 }
732                 else
733                 {
734                   newname = newname + "; " + refs[i].getAccessionId();
735                 }
736               }
737             }
738             if (newname == null)
739             {
740               SequenceFeature sf[] = sq.getSequenceFeatures();
741               for (int i = 0; sf != null && i < sf.length; i++)
742               {
743                 if (sf[i].getType().equals(labelClass))
744                 {
745                   if (newname == null)
746                   {
747                     newname = new String(sf[i].getDescription());
748                   }
749                   else
750                   {
751                     newname = newname + "; " + sf[i].getDescription();
752                   }
753                 }
754               }
755             }
756           }
757           if (newname != null)
758           {
759             String oldname = ((SequenceNode) node).getName();
760             // TODO : save in the undo object for this modification.
761             ((SequenceNode) node).setName(newname);
762           }
763         }
764       }
765     });
766   }
767 }