JAL-2738 copy to spikes/mungo
[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<CommandI>();
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     AlignmentViewport viewport = ap.av;
554     SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
555     AlignmentSorter.sortByTree(viewport.getAlignment(), tree);
556     CommandI undo;
557     undo = new OrderCommand("Tree Sort", oldOrder, viewport.getAlignment());
558
559     ap.paintAlignment(true);
560     return undo;
561   }
562
563   /**
564    * DOCUMENT ME!
565    * 
566    * @param e
567    *          DOCUMENT ME!
568    */
569   @Override
570   public void font_actionPerformed(ActionEvent e)
571   {
572     if (treeCanvas == null)
573     {
574       return;
575     }
576
577     new FontChooser(this);
578   }
579
580   public Font getTreeFont()
581   {
582     return treeCanvas.font;
583   }
584
585   public void setTreeFont(Font f)
586   {
587     if (treeCanvas != null)
588     {
589       treeCanvas.setFont(f);
590     }
591   }
592
593   /**
594    * DOCUMENT ME!
595    * 
596    * @param e
597    *          DOCUMENT ME!
598    */
599   @Override
600   public void distanceMenu_actionPerformed(ActionEvent e)
601   {
602     treeCanvas.setShowDistances(distanceMenu.isSelected());
603   }
604
605   /**
606    * DOCUMENT ME!
607    * 
608    * @param e
609    *          DOCUMENT ME!
610    */
611   @Override
612   public void bootstrapMenu_actionPerformed(ActionEvent e)
613   {
614     treeCanvas.setShowBootstrap(bootstrapMenu.isSelected());
615   }
616
617   /**
618    * DOCUMENT ME!
619    * 
620    * @param e
621    *          DOCUMENT ME!
622    */
623   @Override
624   public void placeholdersMenu_actionPerformed(ActionEvent e)
625   {
626     treeCanvas.setMarkPlaceholders(placeholdersMenu.isSelected());
627   }
628
629   /**
630    * DOCUMENT ME!
631    * 
632    * @param e
633    *          DOCUMENT ME!
634    */
635   @Override
636   public void epsTree_actionPerformed(ActionEvent e)
637   {
638     boolean accurateText = true;
639
640     String renderStyle = jalview.bin.Cache.getDefault("EPS_RENDERING",
641             "Prompt each time");
642
643     // If we need to prompt, and if the GUI is visible then
644     // Prompt for EPS rendering style
645     if (renderStyle.equalsIgnoreCase("Prompt each time")
646             && !(System.getProperty("java.awt.headless") != null && System
647                     .getProperty("java.awt.headless").equals("true")))
648     {
649       EPSOptions eps = new EPSOptions();
650       renderStyle = eps.getValue();
651
652       if (renderStyle == null || eps.cancelled)
653       {
654         return;
655       }
656
657     }
658
659     if (renderStyle.equalsIgnoreCase("text"))
660     {
661       accurateText = false;
662     }
663
664     int width = treeCanvas.getWidth();
665     int height = treeCanvas.getHeight();
666
667     try
668     {
669       JalviewFileChooser chooser = new JalviewFileChooser(
670               ImageMaker.EPS_EXTENSION, ImageMaker.EPS_EXTENSION);
671       chooser.setFileView(new JalviewFileView());
672       chooser.setDialogTitle(
673               MessageManager.getString("label.create_eps_from_tree"));
674       chooser.setToolTipText(MessageManager.getString("action.save"));
675
676       int value = chooser.showSaveDialog(this);
677
678       if (value != JalviewFileChooser.APPROVE_OPTION)
679       {
680         return;
681       }
682
683       Cache.setProperty("LAST_DIRECTORY",
684               chooser.getSelectedFile().getParent());
685
686       FileOutputStream out = new FileOutputStream(
687               chooser.getSelectedFile());
688       EpsGraphics2D pg = new EpsGraphics2D("Tree", out, 0, 0, width,
689               height);
690
691       pg.setAccurateTextMode(accurateText);
692
693       treeCanvas.draw(pg, width, height);
694
695       pg.flush();
696       pg.close();
697     } catch (Exception ex)
698     {
699       ex.printStackTrace();
700     }
701   }
702
703   /**
704    * DOCUMENT ME!
705    * 
706    * @param e
707    *          DOCUMENT ME!
708    */
709   @Override
710   public void pngTree_actionPerformed(ActionEvent e)
711   {
712     int width = treeCanvas.getWidth();
713     int height = treeCanvas.getHeight();
714
715     try
716     {
717       JalviewFileChooser chooser = new JalviewFileChooser(
718               ImageMaker.PNG_EXTENSION, ImageMaker.PNG_DESCRIPTION);
719
720       chooser.setFileView(new jalview.io.JalviewFileView());
721       chooser.setDialogTitle(
722               MessageManager.getString("label.create_png_from_tree"));
723       chooser.setToolTipText(MessageManager.getString("action.save"));
724
725       int value = chooser.showSaveDialog(this);
726
727       if (value != jalview.io.JalviewFileChooser.APPROVE_OPTION)
728       {
729         return;
730       }
731
732       jalview.bin.Cache.setProperty("LAST_DIRECTORY",
733               chooser.getSelectedFile().getParent());
734
735       FileOutputStream out = new FileOutputStream(
736               chooser.getSelectedFile());
737
738       BufferedImage bi = new BufferedImage(width, height,
739               BufferedImage.TYPE_INT_RGB);
740       Graphics png = bi.getGraphics();
741
742       treeCanvas.draw(png, width, height);
743
744       ImageIO.write(bi, "png", out);
745       out.close();
746     } catch (Exception ex)
747     {
748       ex.printStackTrace();
749     }
750   }
751
752   /**
753    * change node labels to the annotation referred to by labelClass TODO:
754    * promote to a datamodel modification that can be undone TODO: make argument
755    * one case of a generic transformation function ie { undoStep = apply(Tree,
756    * TransformFunction)};
757    * 
758    * @param labelClass
759    */
760   public void changeNames(final String labelClass)
761   {
762     tree.applyToNodes(new NodeTransformI()
763     {
764
765       @Override
766       public void transform(BinaryNode node)
767       {
768         if (node instanceof SequenceNode
769                 && !((SequenceNode) node).isPlaceholder()
770                 && !((SequenceNode) node).isDummy())
771         {
772           String newname = null;
773           SequenceI sq = (SequenceI) ((SequenceNode) node).element();
774           if (sq != null)
775           {
776             // search dbrefs, features and annotation
777             DBRefEntry[] refs = jalview.util.DBRefUtils
778                     .selectRefs(sq.getDBRefs(), new String[]
779                     { labelClass.toUpperCase() });
780             if (refs != null)
781             {
782               for (int i = 0; i < refs.length; i++)
783               {
784                 if (newname == null)
785                 {
786                   newname = new String(refs[i].getAccessionId());
787                 }
788                 else
789                 {
790                   newname = newname + "; " + refs[i].getAccessionId();
791                 }
792               }
793             }
794             if (newname == null)
795             {
796               List<SequenceFeature> features = sq.getFeatures()
797                       .getPositionalFeatures(labelClass);
798               for (SequenceFeature feature : features)
799               {
800                 if (newname == null)
801                 {
802                   newname = feature.getDescription();
803                 }
804                 else
805                 {
806                   newname = newname + "; " + feature.getDescription();
807                 }
808               }
809             }
810           }
811           if (newname != null)
812           {
813             // String oldname = ((SequenceNode) node).getName();
814             // TODO : save oldname in the undo object for this modification.
815             ((SequenceNode) node).setName(newname);
816           }
817         }
818       }
819     });
820   }
821
822   /**
823    * Formats a localised title for the tree panel, like
824    * <p>
825    * Neighbour Joining Using BLOSUM62
826    * <p>
827    * For a tree loaded from file, just uses the file name
828    * 
829    * @return
830    */
831   public String getPanelTitle()
832   {
833     if (treeTitle != null)
834     {
835       return treeTitle;
836     }
837
838     /*
839      * i18n description of Neighbour Joining or Average Distance method
840      */
841     String treecalcnm = MessageManager
842             .getString("label.tree_calc_" + treeType.toLowerCase());
843
844     /*
845      * short score model name (long description can be too long)
846      */
847     String smn = scoreModelName;
848
849     /*
850      * put them together as <method> Using <model>
851      */
852     final String ttl = MessageManager.formatMessage("label.treecalc_title",
853             treecalcnm, smn);
854     return ttl;
855   }
856 }