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