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