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