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