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