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