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