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