5b51919ca4b3d0d0817c0aad859e452c900c9c84
[jalview.git] / src / jalview / gui / TreePanel.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8)
3  * Copyright (C) 2012 J Procter, AM Waterhouse, LM Lui, J Engelhardt, 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.getSelectionGroup() != null
277                 && av.getSelectionGroup().getSize() > 1;
278         AlignmentView seqStrings = av.getAlignmentView(selview);
279         if (!selview)
280         {
281           start = 0;
282           end = av.getAlignment().getWidth();
283           seqs = av.getAlignment().getSequencesArray();
284         }
285         else
286         {
287           start = av.getSelectionGroup().getStartRes();
288           end = av.getSelectionGroup().getEndRes() + 1;
289           seqs = av.getSelectionGroup().getSequencesInOrder(
290                   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     }
566     else
567     {
568       treeCanvas.ap.alignFrame
569               .addHistoryItem(sortAlignmentIn(treeCanvas.ap));
570     }
571
572   }
573
574   public CommandI sortAlignmentIn(AlignmentPanel ap)
575   {
576     AlignViewport av = ap.av;
577     SequenceI[] oldOrder = av.getAlignment().getSequencesArray();
578     AlignmentSorter.sortByTree(av.getAlignment(), tree);
579     CommandI undo;
580     undo = new OrderCommand("Tree Sort", oldOrder, av.getAlignment());
581
582     ap.paintAlignment(true);
583     return undo;
584   }
585
586   /**
587    * DOCUMENT ME!
588    * 
589    * @param e
590    *          DOCUMENT ME!
591    */
592   public void font_actionPerformed(ActionEvent e)
593   {
594     if (treeCanvas == null)
595     {
596       return;
597     }
598
599     new FontChooser(this);
600   }
601
602   public Font getTreeFont()
603   {
604     return treeCanvas.font;
605   }
606
607   public void setTreeFont(Font font)
608   {
609     if (treeCanvas != null)
610     {
611       treeCanvas.setFont(font);
612     }
613   }
614
615   /**
616    * DOCUMENT ME!
617    * 
618    * @param e
619    *          DOCUMENT ME!
620    */
621   public void distanceMenu_actionPerformed(ActionEvent e)
622   {
623     treeCanvas.setShowDistances(distanceMenu.isSelected());
624   }
625
626   /**
627    * DOCUMENT ME!
628    * 
629    * @param e
630    *          DOCUMENT ME!
631    */
632   public void bootstrapMenu_actionPerformed(ActionEvent e)
633   {
634     treeCanvas.setShowBootstrap(bootstrapMenu.isSelected());
635   }
636
637   /**
638    * DOCUMENT ME!
639    * 
640    * @param e
641    *          DOCUMENT ME!
642    */
643   public void placeholdersMenu_actionPerformed(ActionEvent e)
644   {
645     treeCanvas.setMarkPlaceholders(placeholdersMenu.isSelected());
646   }
647
648   /**
649    * DOCUMENT ME!
650    * 
651    * @param e
652    *          DOCUMENT ME!
653    */
654   public void epsTree_actionPerformed(ActionEvent e)
655   {
656     boolean accurateText = true;
657
658     String renderStyle = jalview.bin.Cache.getDefault("EPS_RENDERING",
659             "Prompt each time");
660
661     // If we need to prompt, and if the GUI is visible then
662     // Prompt for EPS rendering style
663     if (renderStyle.equalsIgnoreCase("Prompt each time")
664             && !(System.getProperty("java.awt.headless") != null && System
665                     .getProperty("java.awt.headless").equals("true")))
666     {
667       EPSOptions eps = new EPSOptions();
668       renderStyle = eps.getValue();
669
670       if (renderStyle == null || eps.cancelled)
671       {
672         return;
673       }
674
675     }
676
677     if (renderStyle.equalsIgnoreCase("text"))
678     {
679       accurateText = false;
680     }
681
682     int width = treeCanvas.getWidth();
683     int height = treeCanvas.getHeight();
684
685     try
686     {
687       jalview.io.JalviewFileChooser chooser = new jalview.io.JalviewFileChooser(
688               jalview.bin.Cache.getProperty("LAST_DIRECTORY"), new String[]
689               { "eps" }, new String[]
690               { "Encapsulated Postscript" }, "Encapsulated Postscript");
691       chooser.setFileView(new jalview.io.JalviewFileView());
692       chooser.setDialogTitle("Create EPS file from tree");
693       chooser.setToolTipText("Save");
694
695       int value = chooser.showSaveDialog(this);
696
697       if (value != jalview.io.JalviewFileChooser.APPROVE_OPTION)
698       {
699         return;
700       }
701
702       jalview.bin.Cache.setProperty("LAST_DIRECTORY", chooser
703               .getSelectedFile().getParent());
704
705       FileOutputStream out = new FileOutputStream(chooser.getSelectedFile());
706       EpsGraphics2D pg = new EpsGraphics2D("Tree", out, 0, 0, width, height);
707
708       pg.setAccurateTextMode(accurateText);
709
710       treeCanvas.draw(pg, width, height);
711
712       pg.flush();
713       pg.close();
714     } catch (Exception ex)
715     {
716       ex.printStackTrace();
717     }
718   }
719
720   /**
721    * DOCUMENT ME!
722    * 
723    * @param e
724    *          DOCUMENT ME!
725    */
726   public void pngTree_actionPerformed(ActionEvent e)
727   {
728     int width = treeCanvas.getWidth();
729     int height = treeCanvas.getHeight();
730
731     try
732     {
733       jalview.io.JalviewFileChooser chooser = new jalview.io.JalviewFileChooser(
734               jalview.bin.Cache.getProperty("LAST_DIRECTORY"), new String[]
735               { "png" }, new String[]
736               { "Portable network graphics" }, "Portable network graphics");
737
738       chooser.setFileView(new jalview.io.JalviewFileView());
739       chooser.setDialogTitle("Create PNG image from tree");
740       chooser.setToolTipText("Save");
741
742       int value = chooser.showSaveDialog(this);
743
744       if (value != jalview.io.JalviewFileChooser.APPROVE_OPTION)
745       {
746         return;
747       }
748
749       jalview.bin.Cache.setProperty("LAST_DIRECTORY", chooser
750               .getSelectedFile().getParent());
751
752       FileOutputStream out = new FileOutputStream(chooser.getSelectedFile());
753
754       BufferedImage bi = new BufferedImage(width, height,
755               BufferedImage.TYPE_INT_RGB);
756       Graphics png = bi.getGraphics();
757
758       treeCanvas.draw(png, width, height);
759
760       ImageIO.write(bi, "png", out);
761       out.close();
762     } catch (Exception ex)
763     {
764       ex.printStackTrace();
765     }
766   }
767
768   /**
769    * change node labels to the annotation referred to by labelClass TODO:
770    * promote to a datamodel modification that can be undone TODO: make argument
771    * one case of a generic transformation function ie { undoStep = apply(Tree,
772    * TransformFunction)};
773    * 
774    * @param labelClass
775    */
776   public void changeNames(final String labelClass)
777   {
778     tree.applyToNodes(new NodeTransformI()
779     {
780
781       public void transform(BinaryNode node)
782       {
783         if (node instanceof SequenceNode
784                 && !((SequenceNode) node).isPlaceholder()
785                 && !((SequenceNode) node).isDummy())
786         {
787           String newname = null;
788           SequenceI sq = (SequenceI) ((SequenceNode) node).element();
789           if (sq != null)
790           {
791             // search dbrefs, features and annotation
792             DBRefEntry[] refs = jalview.util.DBRefUtils.selectRefs(
793                     sq.getDBRef(), new String[]
794                     { labelClass.toUpperCase() });
795             if (refs != null)
796             {
797               for (int i = 0; i < refs.length; i++)
798               {
799                 if (newname == null)
800                 {
801                   newname = new String(refs[i].getAccessionId());
802                 }
803                 else
804                 {
805                   newname = newname + "; " + refs[i].getAccessionId();
806                 }
807               }
808             }
809             if (newname == null)
810             {
811               SequenceFeature sf[] = sq.getSequenceFeatures();
812               for (int i = 0; sf != null && i < sf.length; i++)
813               {
814                 if (sf[i].getType().equals(labelClass))
815                 {
816                   if (newname == null)
817                   {
818                     newname = new String(sf[i].getDescription());
819                   }
820                   else
821                   {
822                     newname = newname + "; " + sf[i].getDescription();
823                   }
824                 }
825               }
826             }
827           }
828           if (newname != null)
829           {
830             String oldname = ((SequenceNode) node).getName();
831             // TODO : save in the undo object for this modification.
832             ((SequenceNode) node).setName(newname);
833           }
834         }
835       }
836     });
837   }
838 }