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