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