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