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