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