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