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