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