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