Clover icon

Coverage Report

  1. Project Clover database Wed Nov 12 2025 13:01:44 GMT
  2. Package jalview.gui

File TreePanel.java

 

Coverage histogram

../../img/srcFileCovDistChart6.png
37% of files have more coverage

Code metrics

94
388
72
2
1,451
976
137
0.35
5.39
36
1.9

Classes

Class Line # Actions
TreePanel 110 359 125
0.499013849.9%
TreePanel.TreeLoader 645 29 12
0.914893691.5%
 

Contributing tests

This file is covered by 13 tests. .

Source view

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.BorderLayout;
24    import java.awt.CardLayout;
25    import java.awt.Color;
26    import java.awt.Component;
27    import java.awt.Dimension;
28    import java.awt.Font;
29    import java.awt.Graphics;
30    import java.awt.Insets;
31    import java.awt.event.ActionEvent;
32    import java.awt.event.ActionListener;
33    import java.awt.event.ItemEvent;
34    import java.awt.event.KeyAdapter;
35    import java.awt.event.KeyEvent;
36    import java.beans.PropertyChangeEvent;
37    import java.beans.PropertyChangeListener;
38    import java.io.File;
39    import java.io.FileOutputStream;
40    import java.util.ArrayList;
41    import java.util.Arrays;
42    import java.util.List;
43    import java.util.Locale;
44    import java.util.Map;
45    import java.util.Vector;
46   
47    import javax.swing.AbstractButton;
48    import javax.swing.BorderFactory;
49    import javax.swing.Box;
50    import javax.swing.BoxLayout;
51    import javax.swing.ButtonGroup;
52    import javax.swing.Icon;
53    import javax.swing.JMenuItem;
54    import javax.swing.JPanel;
55    import javax.swing.JRadioButtonMenuItem;
56    import javax.swing.JScrollBar;
57    import javax.swing.JScrollPane;
58    import javax.swing.JToggleButton;
59    import javax.swing.border.BevelBorder;
60    import javax.swing.event.InternalFrameAdapter;
61    import javax.swing.event.InternalFrameEvent;
62   
63    import org.jibble.epsgraphics.EpsGraphics2D;
64   
65    import jalview.analysis.AlignmentAnnotationUtils;
66    import jalview.analysis.AlignmentSorter;
67    import jalview.analysis.AlignmentUtils;
68    import jalview.analysis.AnnotationSorter;
69    import jalview.analysis.AverageDistanceTree;
70    import jalview.analysis.NJTree;
71    import jalview.analysis.TreeBuilder;
72    import jalview.analysis.TreeModel;
73    import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder;
74    import jalview.analysis.scoremodels.ScoreModels;
75    import jalview.api.analysis.ScoreModelI;
76    import jalview.api.analysis.SimilarityParamsI;
77    import jalview.bin.Cache;
78    import jalview.bin.Console;
79    import jalview.commands.CommandI;
80    import jalview.commands.OrderCommand;
81    import jalview.datamodel.Alignment;
82    import jalview.datamodel.AlignmentAnnotation;
83    import jalview.datamodel.AlignmentI;
84    import jalview.datamodel.AlignmentView;
85    import jalview.datamodel.BinaryNode;
86    import jalview.datamodel.DBRefEntry;
87    import jalview.datamodel.HiddenColumns;
88    import jalview.datamodel.NodeTransformI;
89    import jalview.datamodel.SequenceFeature;
90    import jalview.datamodel.SequenceI;
91    import jalview.datamodel.SequenceNode;
92    import jalview.gui.ImageExporter.ImageWriterI;
93    import jalview.io.JalviewFileChooser;
94    import jalview.io.JalviewFileView;
95    import jalview.io.NewickFile;
96    import jalview.io.exceptions.ImageOutputException;
97    import jalview.jbgui.GTreePanel;
98    import jalview.util.ImageMaker.TYPE;
99    import jalview.util.ColorUtils;
100    import jalview.util.Constants;
101    import jalview.util.MessageManager;
102    import jalview.viewmodel.AlignmentViewport;
103   
104    /**
105    * DOCUMENT ME!
106    *
107    * @author $author$
108    * @version $Revision$
109    */
 
110    public class TreePanel extends GTreePanel
111    {
112    String treeType;
113   
114    String scoreModelName; // if tree computed
115   
116    String treeTitle; // if tree loaded
117   
118    SimilarityParamsI similarityParams;
119   
120    private TreeCanvas treeCanvas;
121   
122    TreeModel tree;
123   
124    private AlignViewport av;
125   
126    //if tree is based on annotations like secondary structure
127    boolean annotationBased = false;
128   
129    JPanel ssProviderPanel;
130   
131    private Map<String, Color> secondaryStructureProviderColorMap;
132   
133    private static final int GAP_WIDTH = 2; //gap between components
134    private static final int SS_PROVIDER_PANEL_FONT_SIZE = 10;
135    private static final int TOGGLE_BUTTON_ICON_WIDTH = 10;
136    private static final int TOGGLE_BUTTON_ICON_HEIGHT = 10;
137    private static final int SCROLLBAR_HEIGHT = 8;
138    private static final int SCROLL_PANEL_HEIGHT = 30;
139    private static final int TOGGLE_BUTTON_MARGIN = 1;
140    private static final int NO_PADDING = 0;
141    private static final int NO_WIDTH_PREFERENCE = 0;
142   
143    /**
144    * Creates a new TreePanel object.
145    *
146    * @param ap
147    * @param type
148    * @param modelName
149    * @param options
150    */
 
151  1 toggle public TreePanel(AlignmentPanel ap, String type, String modelName,
152    SimilarityParamsI options)
153    {
154  1 super();
155  1 this.setFrameIcon(null);
156  1 this.similarityParams = options;
157  1 initTreePanel(ap, type, modelName, null, null, null, null);
158   
159    // We know this tree has distances. JBPNote TODO: prolly should add this as
160    // a userdefined default
161    // showDistances(true);
162    }
163   
 
164  14 toggle public TreePanel(AlignmentPanel alignPanel, NewickFile newtree,
165    String theTitle, AlignmentView inputData)
166    {
167  14 this(alignPanel, newtree, theTitle, inputData, null, null);
168    }
169   
170    /**
171    * when true, leaf labels are annotations
172    */
173    boolean forAnnotation = false;
174   
 
175  14 toggle public TreePanel(AlignmentPanel alignPanel, NewickFile newtree,
176    String theTitle, AlignmentView inputData,
177    AlignmentAnnotation[] leafAnnotations, String subTitle)
178    {
179  14 super();
180  14 this.forAnnotation = leafAnnotations != null
181    && leafAnnotations.length > 0;
182  14 this.setFrameIcon(null);
183  14 this.treeTitle = theTitle;
184  14 initTreePanel(alignPanel, null, null, newtree, inputData, null, null);
185    }
186   
187    /**
188    * columnwise tree associated with positions in aa
189    *
190    * @param alignPanel
191    * @param fin
192    * @param title
193    * @param aa
194    */
 
195  0 toggle public TreePanel(AlignmentPanel alignPanel, NewickFile fin,
196    AlignmentAnnotation aa, String title)
197    {
198  0 super();
199  0 columnWise = true;
200  0 assocAnnotation = aa;
201  0 this.setFrameIcon(null);
202  0 this.treeTitle = title;
203  0 initTreePanel(alignPanel, null, null, fin, null, null, null);
204    }
205   
 
206  3 toggle public TreePanel(AlignmentPanel alignPanel, NewickFile newtree,
207    String theTitle, AlignmentView inputData,
208    AlignmentAnnotation[] leafAnnotations, String subTitle,
209    Map<String, AlignmentAnnotation> annotationIds)
210    {
211  3 super();
212  3 this.forAnnotation = leafAnnotations != null
213    && leafAnnotations.length > 0;
214  3 this.setFrameIcon(null);
215  3 this.treeTitle = theTitle;
216  3 initTreePanel(alignPanel, null, null, newtree, inputData, null, annotationIds);
217    }
218   
219   
220    boolean columnWise = false;
221   
222    AlignmentAnnotation assocAnnotation = null;
223   
 
224  261 toggle public boolean isColumnWise()
225    {
226  261 return columnWise;
227    }
228   
 
229  34 toggle public boolean isAnnotationBased()
230    {
231  34 return annotationBased;
232    }
233   
 
234  34 toggle public void setAnnotationBased(boolean annotationBased)
235    {
236  34 this.annotationBased = annotationBased;
237    }
238   
 
239  18 toggle public AlignmentAnnotation getAssocAnnotation()
240    {
241  18 return assocAnnotation;
242    }
243   
 
244  0 toggle public AlignmentI getAlignment()
245    {
246  0 return getTreeCanvas().getViewport().getAlignment();
247    }
248   
 
249  0 toggle public AlignmentViewport getViewPort()
250    {
251    // @Mungo - Why don't we return our own viewport ???
252  0 return getTreeCanvas().getViewport();
253    }
254   
255   
 
256  4 toggle public Map<String, Color> getSecondaryStructureProviderColorMap()
257    {
258  4 return secondaryStructureProviderColorMap;
259    }
260   
261    /**This method restore the colour map from the loaded project file.
262    * It replaces the current colour for the labels present in the
263    * loaded project file.
264    * @param colorMap colour map from the loaded project
265    */
 
266  3 toggle public void restoreSecondaryStructureProviderColorMap(Map<String, Color> colorMap)
267    {
268  3 secondaryStructureProviderColorMap = colorMap;
269  3 ColorUtils.restoreMyHSBSpacedColours(colorMap);
270    }
271   
 
272  18 toggle void initTreePanel(AlignmentPanel ap, String type, String modelName,
273    NewickFile newTree, AlignmentView inputData,
274    AlignmentAnnotation[] leafAnnotations, Map<String, AlignmentAnnotation> annotationIds)
275    {
276   
277  18 av = ap.av;
278  18 this.treeType = type;
279  18 this.scoreModelName = modelName;
280   
281  18 treeCanvas = new TreeCanvas(this, ap, scrollPane);
282  18 scrollPane.setViewportView(treeCanvas);
283   
284  18 if (isAnnotationBased() || (similarityParams != null
285    && similarityParams.getSecondaryStructureSource() != null))
286    {
287    //setting showSecondaryStructureProviderMenu to true if the
288    //similarity is based on secondary structure
289  1 showSecondaryStructureProviderMenu.setVisible(true);
290  1 sortAnnotationAssocViews.setVisible(true);
291  1 setAnnotationBased(true);
292    }
293    else {
294    //setting showSecondaryStructureProviderMenu to false if the
295    //similarity is not based on secondary structure
296  17 showSecondaryStructureProviderMenu.setVisible(false);
297  17 sortAnnotationAssocViews.setVisible(false);
298  17 setAnnotationBased(false);
299    }
300  18 if (leafAnnotations != null)
301    {
302  0 forAnnotation = true;
303    }
304  18 if (columnWise)
305    {
306  0 bootstrapMenu.setVisible(false);
307  0 placeholdersMenu.setState(false);
308  0 placeholdersMenu.setVisible(false);
309  0 fitToWindow.setState(false);
310  0 sortAssocViews.setVisible(false);
311  0 sortAnnotationAssocViews.setVisible(false);
312    }
313   
314  18 addKeyListener(new KeyAdapter()
315    {
 
316  0 toggle @Override
317    public void keyPressed(KeyEvent e)
318    {
319  0 switch (e.getKeyCode())
320    {
321  0 case 27: // escape
322  0 treeCanvas.clearSelectedLeaves();
323  0 e.consume();
324  0 break;
325   
326    }
327   
328    }
329    });
330  18 PaintRefresher.Register(this, ap.av.getSequenceSetId());
331   
332  18 buildAssociatedViewMenu();
333   
334  18 final PropertyChangeListener listener = addAlignmentListener();
335   
336    /*
337    * remove listener when window is closed, so that this
338    * panel can be garbage collected
339    */
340  18 addInternalFrameListener(new InternalFrameAdapter()
341    {
 
342  16 toggle @Override
343    public void internalFrameClosed(InternalFrameEvent evt)
344    {
345  16 if (av != null)
346    {
347  16 av.removePropertyChangeListener(listener);
348    }
349  16 releaseReferences();
350    }
351    });
352   
353  18 TreeLoader tl = new TreeLoader(newTree, inputData, leafAnnotations, annotationIds);
354  18 tl.start();
355   
356    }
357   
358   
359    /**
360    * This method adds the secondary structure provider panel. The panel includes
361    * scrollable list of secondary structure providers and their corresponding
362    * toggle buttons with color key. Secondary structure providers toggle buttons
363    * with color key is shown if user selects the option available in
364    * View->Show secondary structure providers -> As coloured lines. And, list of
365    * secondary structure providers in all other cases. A CardLayout is used to
366    * switch between views.
367    */
 
368  5 toggle public void addSSProviderPanel()
369    {
370   
371  5 if(secondaryStructureProviderColorMap == null || secondaryStructureProviderColorMap.isEmpty()) {
372  1 return;
373    }
374   
375    // Panels to display the color map and provider list
376  4 JPanel ssProviderColorMapPanel = new JPanel();
377  4 JScrollPane colorMapScrollPanel;
378   
379    // Set layouts to arrange components horizontally
380  4 ssProviderColorMapPanel.setLayout(new BoxLayout(ssProviderColorMapPanel, BoxLayout.X_AXIS));
381   
382    // Get the color mapping
383  4 List<Map.Entry<String, Color>> sortedSSProviderColorMap = new ArrayList<>(
384    secondaryStructureProviderColorMap.entrySet());
385   
386    // Sort the providers alphabetically by name
387  4 sortedSSProviderColorMap.sort(Map.Entry.comparingByKey());
388   
389    // Add toggle buttons and provider labels in the panels iteratively for each provider
390  4 for (Map.Entry<String, Color> sortedSSProviderColor : sortedSSProviderColorMap)
391    {
392    // Add toggle button panel
393  14 JPanel toggleButtonPanel = createToggleButtonForProvider(sortedSSProviderColor);
394  14 ssProviderColorMapPanel.add(toggleButtonPanel);
395  14 ssProviderColorMapPanel.add(Box.createRigidArea(new Dimension(GAP_WIDTH, 0))); // gap
396    }
397   
398    // Set an empty border for the color map panel with no padding
399  4 ssProviderColorMapPanel.setBorder(BorderFactory.createEmptyBorder(NO_PADDING,
400    NO_PADDING, NO_PADDING, NO_PADDING)); // no padding
401  4 ssProviderColorMapPanel.revalidate();
402  4 ssProviderColorMapPanel.repaint();
403   
404    // Add panels in scroll pane
405  4 colorMapScrollPanel = initializeColorMapScrollPanel(ssProviderColorMapPanel);
406   
407    // Create a CardLayout panel to switch between the panels
408  4 ssProviderPanel = new JPanel(new CardLayout());
409  4 ssProviderPanel.add(colorMapScrollPanel, "colorMapScrollPanel");
410   
411    // Show the panel based on user selection
412  4 CardLayout ssProviderCardLayout = (CardLayout) (ssProviderPanel.getLayout());
413  4 if (showStructureProviderColouredLines.isSelected()) {
414  0 ssProviderCardLayout.show(ssProviderPanel, "colorMapScrollPanel");
415  0 ssProviderPanel.setVisible(true);
416    }
417    else {
418  4 ssProviderPanel.setVisible(false);
419    }
420   
421  4 ssProviderPanel.repaint();
422   
423  4 this.add(ssProviderPanel, BorderLayout.NORTH);
424  4 this.revalidate();
425  4 this.repaint();
426    }
427   
428    /**
429    * Creates a toggle button panel for a secondary structure provider.
430    *
431    * @param colorKey A Map.Entry with the provider name and its colour.
432    * @return A JPanel with the toggle button.
433    */
 
434  14 toggle private JPanel createToggleButtonForProvider(Map.Entry<String, Color> colorKey)
435    {
436  14 String provider = colorKey.getKey();
437  14 Color color = colorKey.getValue();
438   
439  14 JToggleButton toggleButton = createToggleButton(provider, color);
440  14 JPanel toggleButtonPanel = new JPanel();
441  14 toggleButtonPanel.setLayout(new BoxLayout(toggleButtonPanel, BoxLayout.X_AXIS));
442  14 toggleButtonPanel.add(toggleButton);
443  14 toggleButtonPanel.add(Box.createRigidArea(new Dimension(GAP_WIDTH, 0))); // gap
444   
445  14 return toggleButtonPanel;
446    }
447   
448   
449    /**
450    * Creates a toggle button for a secondary structure provider with
451    * corresponding coloured icon. The secondary structure provider lines
452    * are coloured if the button is OFF (default). The lines are grayed
453    * if the button is ON.
454    *
455    * @param provider The name of the secondary structure provider.
456    * @param color The colour mapped with the secondary structure provider.
457    * @return JToggleButton with the secondary structure provider name
458    * and colour icon.
459    */
 
460  14 toggle private JToggleButton createToggleButton(String provider, Color color)
461    {
462  14 JToggleButton ssProviderToggleButton = new JToggleButton(provider);
463  14 ssProviderToggleButton.setFont(new Font(getTreeFont().getName(),
464    getTreeFont().getStyle(), SS_PROVIDER_PANEL_FONT_SIZE));
465  14 ssProviderToggleButton.setMargin(
466    new Insets(TOGGLE_BUTTON_MARGIN, TOGGLE_BUTTON_MARGIN,
467    TOGGLE_BUTTON_MARGIN, TOGGLE_BUTTON_MARGIN));
468   
469    // Icon to display the secondary structure provider colour
470  14 Icon toggleButtonColorIcon = new Icon()
471    {
 
472  0 toggle @Override
473    public void paintIcon(Component c, Graphics g, int x, int y)
474    {
475  0 g.setColor(color);
476  0 g.fillRect(x, y, getIconWidth(), getIconHeight());
477    }
478   
 
479  84 toggle @Override
480    public int getIconWidth()
481    {
482  84 return TOGGLE_BUTTON_ICON_WIDTH;
483    }
484   
 
485  84 toggle @Override
486    public int getIconHeight()
487    {
488  84 return TOGGLE_BUTTON_ICON_HEIGHT;
489    }
490    };
491   
492  14 ssProviderToggleButton.setIcon(toggleButtonColorIcon);
493  14 ssProviderToggleButton
494    .setBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED));
495  14 ssProviderToggleButton.setIconTextGap(GAP_WIDTH);
496  14 ssProviderToggleButton.setSelected(false); // Set default to OFF
497   
498    // button listener
499  14 ssProviderToggleButton.addItemListener(e -> {
500  0 boolean selected = (e.getStateChange() == ItemEvent.SELECTED);
501  0 toggleStructureProviderColouredLine(provider, selected);
502    });
503   
504  14 return ssProviderToggleButton;
505    }
506   
507    /**
508    * Initializes a scroll panel to wrap the given JPanel to include in the tree panel.
509    *
510    * @param jPanel The JPanel to wrap in a scroll pane.
511    * @return A JScrollPane containing the given JPanel.
512    */
 
513  4 toggle private JScrollPane initializeColorMapScrollPanel(JPanel jPanel)
514    {
515  4 JScrollPane scrollPanel = new JScrollPane(jPanel);
516  4 scrollPanel.setPreferredSize(new Dimension(NO_WIDTH_PREFERENCE, SCROLL_PANEL_HEIGHT));
517  4 scrollPanel.setHorizontalScrollBarPolicy(
518    JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
519   
520  4 JScrollBar horizontalScrollBar = scrollPanel
521    .getHorizontalScrollBar();
522  4 horizontalScrollBar.setPreferredSize(
523    new Dimension(NO_WIDTH_PREFERENCE, SCROLLBAR_HEIGHT));
524   
525  4 return scrollPanel;
526    }
527   
528   
529   
530    /**
531    * Ensure any potentially large object references are nulled
532    */
 
533  16 toggle public void releaseReferences()
534    {
535  16 this.tree = null;
536  16 this.treeCanvas.tree = null;
537  16 this.treeCanvas.nodeHash = null;
538  16 this.treeCanvas.nameHash = null;
539    }
540   
541    /**
542    * @return
543    */
 
544  18 toggle protected PropertyChangeListener addAlignmentListener()
545    {
546  18 final PropertyChangeListener listener = new PropertyChangeListener()
547    {
 
548  0 toggle @Override
549    public void propertyChange(PropertyChangeEvent evt)
550    {
551  0 if (evt.getPropertyName().equals("alignment"))
552    {
553  0 if (tree == null)
554    {
555  0 jalview.bin.Console.outPrintln("tree is null");
556    // TODO: deal with case when a change event is received whilst a
557    // tree is still being calculated - should save reference for
558    // processing message later.
559  0 return;
560    }
561  0 if (evt.getNewValue() == null)
562    {
563  0 jalview.bin.Console.outPrintln(
564    "new alignment sequences vector value is null");
565    }
566   
567  0 tree.updatePlaceHolders((List<SequenceI>) evt.getNewValue());
568  0 treeCanvas.nameHash.clear(); // reset the mapping between canvas
569    // rectangles and leafnodes
570  0 repaint();
571    }
572    }
573    };
574  18 av.addPropertyChangeListener(listener);
575  18 return listener;
576    }
577   
 
578  0 toggle @Override
579    public void viewMenu_menuSelected()
580    {
581  0 buildAssociatedViewMenu();
582    }
583   
 
584  18 toggle void buildAssociatedViewMenu()
585    {
586  18 AlignmentPanel[] aps = PaintRefresher
587    .getAssociatedPanels(av.getSequenceSetId());
588  18 if (aps.length == 1 && getTreeCanvas().getAssociatedPanel() == aps[0])
589    {
590  10 associateLeavesMenu.setVisible(false);
591  10 return;
592    }
593   
594  8 associateLeavesMenu.setVisible(true);
595   
596  8 if ((viewMenu
597    .getItem(viewMenu.getItemCount() - 2) instanceof JMenuItem))
598    {
599  8 viewMenu.insertSeparator(viewMenu.getItemCount() - 1);
600    }
601   
602  8 associateLeavesMenu.removeAll();
603   
604  8 JRadioButtonMenuItem item;
605  8 ButtonGroup buttonGroup = new ButtonGroup();
606  8 int i, iSize = aps.length;
607  8 final TreePanel thisTreePanel = this;
608  32 for (i = 0; i < iSize; i++)
609    {
610  24 final AlignmentPanel ap = aps[i];
611  24 item = new JRadioButtonMenuItem(ap.av.getViewName(),
612    ap == treeCanvas.getAssociatedPanel());
613  24 buttonGroup.add(item);
614  24 item.addActionListener(new ActionListener()
615    {
 
616  0 toggle @Override
617    public void actionPerformed(ActionEvent evt)
618    {
619  0 treeCanvas.applyToAllViews = false;
620  0 treeCanvas.setAssociatedPanel(ap);
621  0 treeCanvas.setViewport(ap.av);
622  0 PaintRefresher.Register(thisTreePanel, ap.av.getSequenceSetId());
623    }
624    });
625   
626  24 associateLeavesMenu.add(item);
627    }
628   
629  8 final JRadioButtonMenuItem itemf = new JRadioButtonMenuItem(
630    MessageManager.getString("label.all_views"));
631  8 buttonGroup.add(itemf);
632  8 itemf.setSelected(treeCanvas.applyToAllViews);
633  8 itemf.addActionListener(new ActionListener()
634    {
 
635  0 toggle @Override
636    public void actionPerformed(ActionEvent evt)
637    {
638  0 treeCanvas.applyToAllViews = itemf.isSelected();
639    }
640    });
641  8 associateLeavesMenu.add(itemf);
642   
643    }
644   
 
645    class TreeLoader extends Thread
646    {
647    private NewickFile newtree;
648   
649    private AlignmentView odata = null;
650   
651    private AlignmentAnnotation[] leafAnnotations;
652   
653    private Map<String, AlignmentAnnotation> annotationIds = null;
654   
 
655  18 toggle public TreeLoader(NewickFile newickFile, AlignmentView inputData,
656    AlignmentAnnotation[] leafAnnotations,
657    Map<String, AlignmentAnnotation> annotationIds)
658    {
659  18 this.newtree = newickFile;
660  18 this.odata = inputData;
661  18 this.leafAnnotations = leafAnnotations;
662  18 this.annotationIds = annotationIds;
663   
664  18 if (newickFile != null)
665    {
666    // Must be outside run(), as Jalview2XML tries to
667    // update distance/bootstrap visibility at the same time
668  17 showBootstrap(newickFile.HasBootstrap());
669  17 showDistances(newickFile.HasDistances());
670    }
671    }
672   
 
673  18 toggle @Override
674    public void run()
675    {
676   
677  18 if (newtree != null)
678    {
679  17 tree = new TreeModel(av.getAlignment().getSequencesArray(), odata,
680    newtree, leafAnnotations);
681  17 if (tree.getOriginalData() == null)
682    {
683  17 originalSeqData.setVisible(false);
684    }
685    }
686    else
687    {
688  1 ScoreModelI sm = ScoreModels.getInstance().getScoreModel(
689    scoreModelName, treeCanvas.getAssociatedPanel());
690  1 TreeBuilder njtree = treeType.equals(TreeBuilder.NEIGHBOUR_JOINING)
691    ? new NJTree(av, sm, similarityParams)
692    : new AverageDistanceTree(av, sm, similarityParams);
693  1 List<String> labels = njtree.getLabels();
694  1 if(labels != null && labels.size()>0) {
695  1 secondaryStructureProviderColorMap = AlignmentUtils.assignColorsForSecondaryStructureProviders(labels);
696  1 if(secondaryStructureProviderColorMap.size()>0) {
697  1 addSSProviderPanel();
698    }
699    }
700  1 tree = new TreeModel(njtree);
701    // don't display distances for columnwise trees
702   
703    }
704   
705    // Process tree nodes to map annotations if annotation ids are present
706  18 if (annotationIds != null && annotationIds.size() > 0)
707    {
708  3 TreePanel.processTreeNodes(tree, annotationIds);
709    }
710   
711  18 showDistances(!columnWise);
712  18 tree.reCount(tree.getTopNode());
713  18 tree.findHeight(tree.getTopNode());
714  18 treeCanvas.setTree(tree);
715  18 treeCanvas.repaint();
716  18 av.setCurrentTree(tree);
717  18 if (av.getSortByTree())
718    {
719  1 sortByTree_actionPerformed();
720    }
721    }
722    }
723   
 
724  38 toggle public void showDistances(boolean b)
725    {
726  38 treeCanvas.setShowDistances(b);
727  38 distanceMenu.setSelected(b);
728    }
729   
 
730  20 toggle public void showBootstrap(boolean b)
731    {
732  20 treeCanvas.setShowBootstrap(b);
733  20 bootstrapMenu.setSelected(b);
734    }
735   
 
736  3 toggle public void showPlaceholders(boolean b)
737    {
738  3 placeholdersMenu.setState(b);
739  3 treeCanvas.setMarkPlaceholders(b);
740    }
741   
742    /**
743    * DOCUMENT ME!
744    *
745    * @return DOCUMENT ME!
746    */
 
747  19 toggle public TreeModel getTree()
748    {
749  19 return tree;
750    }
751   
752    /**
753    * DOCUMENT ME!
754    *
755    * @param e
756    * DOCUMENT ME!
757    */
 
758  0 toggle @Override
759    public void textbox_actionPerformed(ActionEvent e)
760    {
761  0 CutAndPasteTransfer cap = new CutAndPasteTransfer();
762   
763  0 String newTitle = getPanelTitle();
764   
765  0 NewickFile fout = new NewickFile(tree.getTopNode());
766  0 try
767    {
768  0 cap.setText(fout.print(tree.hasBootstrap(), tree.hasDistances(),
769    tree.hasRootDistance()));
770  0 Desktop.addInternalFrame(cap, newTitle, 500, 100);
771    } catch (OutOfMemoryError oom)
772    {
773  0 new OOMWarning("generating newick tree file", oom);
774  0 cap.dispose();
775    }
776   
777    }
778   
779    /**
780    * DOCUMENT ME!
781    *
782    * @param e
783    * DOCUMENT ME!
784    */
 
785  0 toggle @Override
786    public void saveAsNewick_actionPerformed(ActionEvent e)
787    {
788    // TODO: JAL-3048 save newick file for Jalview-JS
789  0 JalviewFileChooser chooser = new JalviewFileChooser(
790    Cache.getProperty("LAST_DIRECTORY"));
791  0 chooser.setFileView(new JalviewFileView());
792  0 chooser.setDialogTitle(
793    MessageManager.getString("label.save_tree_as_newick"));
794  0 chooser.setToolTipText(MessageManager.getString("action.save"));
795   
796  0 int value = chooser.showSaveDialog(null);
797   
798  0 if (value == JalviewFileChooser.APPROVE_OPTION)
799    {
800  0 String choice = chooser.getSelectedFile().getPath();
801  0 Cache.setProperty("LAST_DIRECTORY",
802    chooser.getSelectedFile().getParent());
803   
804  0 try
805    {
806  0 jalview.io.NewickFile fout = new jalview.io.NewickFile(
807    tree.getTopNode());
808  0 String output = fout.print(tree.hasBootstrap(), tree.hasDistances(),
809    tree.hasRootDistance());
810  0 java.io.PrintWriter out = new java.io.PrintWriter(
811    new java.io.FileWriter(choice));
812  0 out.println(output);
813  0 out.close();
814    } catch (Exception ex)
815    {
816  0 ex.printStackTrace();
817    }
818    }
819    }
820   
821    /**
822    * DOCUMENT ME!
823    *
824    * @param e
825    * DOCUMENT ME!
826    */
 
827  0 toggle @Override
828    public void printMenu_actionPerformed(ActionEvent e)
829    {
830    // Putting in a thread avoids Swing painting problems
831  0 treeCanvas.startPrinting();
832    }
833   
 
834  0 toggle @Override
835    public void originalSeqData_actionPerformed(ActionEvent e)
836    {
837  0 AlignmentView originalData = tree.getOriginalData();
838  0 if (originalData == null)
839    {
840  0 Console.info(
841    "Unexpected call to originalSeqData_actionPerformed - should have hidden this menu action.");
842  0 return;
843    }
844    // decide if av alignment is sufficiently different to original data to
845    // warrant a new window to be created
846    // create new alignmnt window with hidden regions (unhiding hidden regions
847    // yields unaligned seqs)
848    // or create a selection box around columns in alignment view
849    // test Alignment(SeqCigar[])
850  0 char gc = '-';
851  0 try
852    {
853    // we try to get the associated view's gap character
854    // but this may fail if the view was closed...
855  0 gc = av.getGapCharacter();
856   
857    } catch (Exception ex)
858    {
859    }
860   
861  0 Object[] alAndColsel = originalData.getAlignmentAndHiddenColumns(gc);
862   
863  0 if (alAndColsel != null && alAndColsel[0] != null)
864    {
865    // AlignmentOrder origorder = new AlignmentOrder(alAndColsel[0]);
866   
867  0 AlignmentI al = new Alignment((SequenceI[]) alAndColsel[0]);
868  0 AlignmentI dataset = (av != null && av.getAlignment() != null)
869    ? av.getAlignment().getDataset()
870    : null;
871  0 if (dataset != null)
872    {
873  0 al.setDataset(dataset);
874    }
875   
876  0 if (true)
877    {
878    // make a new frame!
879  0 AlignFrame af = new AlignFrame(al, (HiddenColumns) alAndColsel[1],
880    AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
881   
882    // >>>This is a fix for the moment, until a better solution is
883    // found!!<<<
884    // af.getFeatureRenderer().transferSettings(alignFrame.getFeatureRenderer());
885   
886    // af.addSortByOrderMenuItem(ServiceName + " Ordering",
887    // msaorder);
888   
889  0 Desktop.addInternalFrame(af, MessageManager.formatMessage(
890    "label.original_data_for_params", new Object[]
891    { this.title }), AlignFrame.DEFAULT_WIDTH,
892    AlignFrame.DEFAULT_HEIGHT);
893    }
894    }
895    }
896   
897    /**
898    * DOCUMENT ME!
899    *
900    * @param e
901    * DOCUMENT ME!
902    */
 
903  3 toggle @Override
904    public void fitToWindow_actionPerformed(ActionEvent e)
905    {
906  3 treeCanvas.fitToWindow = fitToWindow.isSelected();
907  3 repaint();
908    }
909   
910    /** This method restores the menu options and view settings
911    * in the tree panel if the tree is based on annotation.
912    *
913    * @param viewName The type of annotation view in the loaded project
914    * : None, Labels, Coloured Lines.
915    */
 
916  4 toggle public void setShowAnnotationAs(String viewName)
917    {
918   
919  4 if (isAnnotationBased())
920    {
921    //setting showSecondaryStructureProviderMenu to true if the
922    //similarity is based on secondary structure
923  4 addSSProviderPanel();
924  4 showSecondaryStructureProviderMenu.setVisible(true);
925  4 setShowAnnotationAsView(viewName);
926  4 sortAnnotationAssocViews.setVisible(true);
927   
928    }
929  4 this.treeCanvas.revalidate();
930  4 revalidate();
931  4 repaint();
932    }
933   
934    /** This method restore the annotation view based on the view name
935    *
936    * @param viewName Name of the view name (for None, Labels, Coloured Lines)
937    * which matches with the text displayed in the button.
938    */
 
939  4 toggle public void setShowAnnotationAsView(String viewName)
940    {
941  4 for (AbstractButton button : Arrays.asList(showStructureProviderLabels,
942    showStructureProviderColouredLines, hideStructureProviders)) {
943  8 if (button.getText().equals(viewName)) {
944  4 button.doClick(); // Triggers the action event
945  4 return;
946    }
947    }
948    // The default view - None
949  0 hideStructureProviders.doClick();
950    }
951   
952    /**
953    * This method retrieves the text in the selected view button (for None,
954    * Labels, Coloured Lines) in the tree panel and returns it.
955    * @return The text in the selected view button
956    */
 
957  4 toggle public String getShowAnnotationAs() {
958  4 for (AbstractButton button : Arrays.asList(showStructureProviderLabels, showStructureProviderColouredLines, hideStructureProviders)) {
959  8 if (button.isSelected()) {
960  4 return button.getText(); // Gets the text
961    }
962    }
963  0 return hideStructureProviders.getText();
964    }
965   
966   
967    /**
968    * sort the associated alignment view by the current tree.
969    *
970    * @param e
971    */
 
972  1 toggle @Override
973    public void sortByTree_actionPerformed()
974    {
975   
976  1 if (treeCanvas.applyToAllViews)
977    {
978  0 final ArrayList<CommandI> commands = new ArrayList<>();
979  0 for (AlignmentPanel ap : PaintRefresher
980    .getAssociatedPanels(av.getSequenceSetId()))
981    {
982  0 commands.add(sortAlignmentIn(ap.av.getAlignPanel()));
983    }
984  0 av.getAlignPanel().alignFrame.addHistoryItem(new CommandI()
985    {
986   
 
987  0 toggle @Override
988    public void undoCommand(AlignmentI[] views)
989    {
990  0 for (CommandI tsort : commands)
991    {
992  0 tsort.undoCommand(views);
993    }
994    }
995   
 
996  0 toggle @Override
997    public int getSize()
998    {
999  0 return commands.size();
1000    }
1001   
 
1002  0 toggle @Override
1003    public String getDescription()
1004    {
1005  0 return "Tree Sort (many views)";
1006    }
1007   
 
1008  0 toggle @Override
1009    public void doCommand(AlignmentI[] views)
1010    {
1011   
1012  0 for (CommandI tsort : commands)
1013    {
1014  0 tsort.doCommand(views);
1015    }
1016    }
1017    });
1018  0 for (AlignmentPanel ap : PaintRefresher
1019    .getAssociatedPanels(av.getSequenceSetId()))
1020    {
1021    // ensure all the alignFrames refresh their GI after adding an undo item
1022  0 ap.alignFrame.updateEditMenuBar();
1023    }
1024    }
1025    else
1026    {
1027  1 treeCanvas.getAssociatedPanel().alignFrame.addHistoryItem(
1028    sortAlignmentIn(treeCanvas.getAssociatedPanel()));
1029    }
1030   
1031    }
1032   
1033    /**
1034    * This method sort the annotation panel by tree. The sorted annotation rows
1035    * from tree appear first followed by other annotation rows in the original
1036    * order. Sort annotation by sequence/label overrides sort annotation by tree.
1037    */
 
1038  0 toggle @Override
1039    public void sortAnnotationByTree_actionPerformed()
1040    {
1041  0 if (getAlignment() != null
1042    && getAlignment().getAlignmentAnnotation() != null
1043    && getAlignment().getAlignmentAnnotation().length > 0)
1044    {
1045    // Initialize annotation sorter with the alignment
1046  0 final AnnotationSorter sorter = new AnnotationSorter(getAlignment(),
1047    av.isShowAutocalculatedAbove());
1048   
1049    // Sort by tree
1050  0 sorter.sortByTree(getAlignment().getAlignmentAnnotation(), tree);
1051  0 av.setSortAnnotationsBy(SequenceAnnotationOrder.NONE);
1052    // Repaint the alignment panel after the sort
1053  0 av.getAlignPanel().repaint();
1054    }
1055    }
1056   
1057   
 
1058  1 toggle public CommandI sortAlignmentIn(AlignmentPanel ap)
1059    {
1060    // TODO: move to alignment view controller
1061  1 AlignmentViewport viewport = ap.av;
1062  1 SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
1063  1 AlignmentSorter.sortByTree(viewport.getAlignment(), tree);
1064  1 CommandI undo;
1065  1 undo = new OrderCommand("Tree Sort", oldOrder, viewport.getAlignment());
1066   
1067  1 ap.paintAlignment(true, false);
1068  1 return undo;
1069    }
1070   
1071    /**
1072    * DOCUMENT ME!
1073    *
1074    * @param e
1075    * DOCUMENT ME!
1076    */
 
1077  0 toggle @Override
1078    public void font_actionPerformed(ActionEvent e)
1079    {
1080  0 if (treeCanvas == null)
1081    {
1082  0 return;
1083    }
1084   
1085  0 new FontChooser(this);
1086    }
1087   
 
1088  46 toggle public Font getTreeFont()
1089    {
1090  46 return treeCanvas.font;
1091    }
1092   
 
1093  3 toggle public void setTreeFont(Font f)
1094    {
1095  3 if (treeCanvas != null)
1096    {
1097  3 treeCanvas.setFont(f);
1098    }
1099    }
1100   
1101    /**
1102    * DOCUMENT ME!
1103    *
1104    * @param e
1105    * DOCUMENT ME!
1106    */
 
1107  0 toggle @Override
1108    public void distanceMenu_actionPerformed(ActionEvent e)
1109    {
1110  0 treeCanvas.setShowDistances(distanceMenu.isSelected());
1111    }
1112   
 
1113  0 toggle @Override
1114    public void hideStructureProviders_actionPerformed(ActionEvent e)
1115    {
1116  0 treeCanvas.hideStructureProviders(hideStructureProviders.isSelected());
1117   
1118    // Show the panel based on user selection
1119  0 CardLayout ssProviderCardLayout = (CardLayout) (ssProviderPanel.getLayout());
1120  0 if (showStructureProviderColouredLines.isSelected()) {
1121  0 ssProviderCardLayout.show(ssProviderPanel, "colorMapScrollPanel");
1122  0 ssProviderPanel.setVisible(true);
1123    }
1124    else {
1125  0 ssProviderPanel.setVisible(false);
1126    }
1127  0 ssProviderPanel.repaint();
1128    }
1129   
 
1130  4 toggle @Override
1131    public void showStructureProviderColouredLines_actionPerformed(ActionEvent e)
1132    {
1133  4 treeCanvas.setShowStructureProviderColouredLines(showStructureProviderColouredLines.isSelected());
1134   
1135    // Show the panel based on user selection
1136  4 CardLayout ssProviderCardLayout = (CardLayout) (ssProviderPanel.getLayout());
1137   
1138  4 if (showStructureProviderColouredLines.isSelected()) {
1139  4 ssProviderCardLayout.show(ssProviderPanel, "colorMapScrollPanel");
1140  4 ssProviderPanel.setVisible(true);
1141    }
1142    else {
1143  0 ssProviderPanel.setVisible(false);
1144    }
1145  4 ssProviderPanel.repaint();
1146    }
1147   
 
1148  0 toggle @Override
1149    public void showStructureProviderLabels_actionPerformed(ActionEvent e)
1150    {
1151  0 treeCanvas.setShowStructureProviderLabels(showStructureProviderLabels.isSelected());
1152   
1153    // Show the panel based on user selection
1154  0 CardLayout ssProviderCardLayout = (CardLayout) (ssProviderPanel.getLayout());
1155  0 if (showStructureProviderColouredLines.isSelected()) {
1156  0 ssProviderCardLayout.show(ssProviderPanel, "colorMapScrollPanel");
1157  0 ssProviderPanel.setVisible(true);
1158    }
1159    else {
1160  0 ssProviderPanel.setVisible(false);
1161    }
1162  0 ssProviderPanel.repaint();
1163    }
1164   
1165   
 
1166  0 toggle public void toggleStructureProviderColouredLine(String provider, boolean action)
1167    {
1168  0 treeCanvas.toggleStructureProviderColouredLine(provider, action);
1169    }
1170   
1171    /**
1172    * DOCUMENT ME!
1173    *
1174    * @param e
1175    * DOCUMENT ME!
1176    */
 
1177  0 toggle @Override
1178    public void bootstrapMenu_actionPerformed(ActionEvent e)
1179    {
1180  0 treeCanvas.setShowBootstrap(bootstrapMenu.isSelected());
1181    }
1182   
1183    /**
1184    * DOCUMENT ME!
1185    *
1186    * @param e
1187    * DOCUMENT ME!
1188    */
 
1189  0 toggle @Override
1190    public void placeholdersMenu_actionPerformed(ActionEvent e)
1191    {
1192  0 treeCanvas.setMarkPlaceholders(placeholdersMenu.isSelected());
1193    }
1194   
1195    /**
1196    * Outputs the Tree in image format (currently EPS or PNG). The user is
1197    * prompted for the file to save to, and for EPS (unless a preference is
1198    * already set) for the choice of Text or Lineart for character rendering.
1199    */
 
1200  0 toggle @Override
1201    public void writeTreeImage(TYPE imageFormat)
1202    {
1203  0 int width = treeCanvas.getWidth();
1204  0 int height = treeCanvas.getHeight();
1205  0 ImageWriterI writer = new ImageWriterI()
1206    {
 
1207  0 toggle @Override
1208    public void exportImage(Graphics g) throws Exception
1209    {
1210  0 treeCanvas.draw(g, width, height);
1211    }
1212    };
1213  0 String tree = MessageManager.getString("label.tree");
1214  0 ImageExporter exporter = new ImageExporter(writer, null, imageFormat,
1215    tree);
1216  0 try
1217    {
1218  0 exporter.doExport(null, this, width, height,
1219    tree.toLowerCase(Locale.ROOT));
1220    } catch (ImageOutputException ioex)
1221    {
1222  0 Console.error(
1223    "Unexpected error whilst writing " + imageFormat.toString(),
1224    ioex);
1225    }
1226    }
1227   
1228    /**
1229    * change node labels to the annotation referred to by labelClass TODO:
1230    * promote to a datamodel modification that can be undone TODO: make argument
1231    * one case of a generic transformation function ie { undoStep = apply(Tree,
1232    * TransformFunction)};
1233    *
1234    * @param labelClass
1235    */
 
1236  0 toggle public void changeNames(final String labelClass)
1237    {
1238  0 tree.applyToNodes(new NodeTransformI()
1239    {
1240   
 
1241  0 toggle @Override
1242    public void transform(BinaryNode node)
1243    {
1244  0 if (node instanceof SequenceNode
1245    && !((SequenceNode) node).isPlaceholder()
1246    && !((SequenceNode) node).isDummy())
1247    {
1248  0 String newname = null;
1249  0 SequenceI sq = (SequenceI) ((BinaryNode) node).element();
1250  0 if (sq != null)
1251    {
1252    // search dbrefs, features and annotation
1253  0 List<DBRefEntry> refs = jalview.util.DBRefUtils
1254    .selectRefs(sq.getDBRefs(), new String[]
1255    { labelClass.toUpperCase(Locale.ROOT) });
1256  0 if (refs != null)
1257    {
1258  0 for (int i = 0, ni = refs.size(); i < ni; i++)
1259    {
1260  0 if (newname == null)
1261    {
1262  0 newname = new String(refs.get(i).getAccessionId());
1263    }
1264    else
1265    {
1266  0 newname += "; " + refs.get(i).getAccessionId();
1267    }
1268    }
1269    }
1270  0 if (newname == null)
1271    {
1272  0 List<SequenceFeature> features = sq.getFeatures()
1273    .getPositionalFeatures(labelClass);
1274  0 for (SequenceFeature feature : features)
1275    {
1276  0 if (newname == null)
1277    {
1278  0 newname = feature.getDescription();
1279    }
1280    else
1281    {
1282  0 newname = newname + "; " + feature.getDescription();
1283    }
1284    }
1285    }
1286    }
1287  0 if (newname != null)
1288    {
1289    // String oldname = ((SequenceNode) node).getName();
1290    // TODO : save oldname in the undo object for this modification.
1291  0 ((BinaryNode) node).setName(newname);
1292    }
1293    }
1294    }
1295    });
1296    }
1297   
1298    /**
1299    * Formats a localised title for the tree panel, like
1300    * <p>
1301    * Neighbour Joining Using BLOSUM62
1302    * <p>
1303    * For a tree loaded from file, just uses the file name
1304    *
1305    * @return
1306    */
 
1307  0 toggle public String getPanelTitle()
1308    {
1309  0 if (treeTitle != null)
1310    {
1311  0 return treeTitle;
1312    }
1313   
1314    /*
1315    * i18n description of Neighbour Joining or Average Distance method
1316    */
1317  0 String treecalcnm = MessageManager.getString(
1318    "label.tree_calc_" + treeType.toLowerCase(Locale.ROOT));
1319   
1320    /*
1321    * short score model name (long description can be too long)
1322    */
1323  0 String smn = scoreModelName;
1324   
1325    /*
1326    * put them together as <method> Using <model>
1327    */
1328  0 final String ttl = MessageManager.formatMessage("label.calc_title",
1329    treecalcnm, smn);
1330  0 return ttl;
1331    }
1332   
1333    /**
1334    * Builds an EPS image and writes it to the specified file.
1335    *
1336    * @param outFile
1337    * @param textOption
1338    * true for Text character rendering, false for Lineart
1339    */
 
1340  0 toggle protected void writeEpsFile(File outFile, boolean textOption)
1341    {
1342  0 try
1343    {
1344  0 int width = treeCanvas.getWidth();
1345  0 int height = treeCanvas.getHeight();
1346   
1347  0 FileOutputStream out = new FileOutputStream(outFile);
1348  0 EpsGraphics2D pg = new EpsGraphics2D("Tree", out, 0, 0, width,
1349    height);
1350  0 pg.setAccurateTextMode(!textOption);
1351  0 treeCanvas.draw(pg, width, height);
1352   
1353  0 pg.flush();
1354  0 pg.close();
1355    } catch (Exception ex)
1356    {
1357  0 jalview.bin.Console.errPrintln("Error writing tree as EPS");
1358  0 ex.printStackTrace();
1359    }
1360    }
1361   
 
1362  0 toggle public AlignViewport getViewport()
1363    {
1364  0 return av;
1365    }
1366   
 
1367  0 toggle public void setViewport(AlignViewport av)
1368    {
1369  0 this.av = av;
1370    }
1371   
 
1372  47 toggle public TreeCanvas getTreeCanvas()
1373    {
1374  47 return treeCanvas;
1375    }
1376   
1377    /** This method maps the tree nodes with annotation rows and respective sequences.
1378    *
1379    * @param alignPanel Alignment panel object
1380    * @param nf Loaded newick file
1381    * @param annotationIds Map of annotation ids and respective annotation rows
1382    * @param treeTitle Title of the tree panel
1383    * @param input Alignment view object
1384    * @return Returns tree panel with the nodes mapped with annotation rows and sequences
1385    */
 
1386  3 toggle public static TreePanel newTreeForAnnotations(AlignmentPanel alignPanel,
1387    NewickFile nf, Map<String, AlignmentAnnotation> annotationIds,
1388    String treeTitle, AlignmentView input)
1389    {
1390    // Create TreePanel instance
1391  3 TreePanel tp = new TreePanel(alignPanel, nf, treeTitle, input, null,
1392    "Annotation Tree", annotationIds);
1393   
1394  3 return tp;
1395    }
1396   
1397   
1398    /**
1399    * Processes tree nodes by applying transformations based on annotations.
1400    * Maps the nodes with annotation rows and/or sequences
1401    * @param tree
1402    * @param annotationIds
1403    */
 
1404  3 toggle private static void processTreeNodes(TreeModel tree,
1405    Map<String, AlignmentAnnotation> annotationIds)
1406    {
1407    // Initialise the node attribute in the tree model
1408    // object with the leaf nodes
1409  3 BinaryNode topNode = tree.getTopNode();
1410  3 Vector<BinaryNode> leafNodes = tree.findLeaves(topNode);
1411  3 tree.setNode(leafNodes);
1412   
1413    //Apply transformation: map nodes with annotation rows and sequences
1414  3 tree.applyToNodes(node -> {
1415  64 Object element = node.element();
1416  64 String nodeName = node.getName(); //Annotation id in case of annotation based tree
1417   
1418    // Extract annotation ID
1419  64 String annotId = nodeName.replaceAll("[^a-zA-Z0-9]", "");
1420   
1421    //Get the annotation object
1422  64 AlignmentAnnotation annot = annotationIds.get(annotId);
1423   
1424  64 if (annot != null)
1425    {
1426    // Map the sequence and annotation with the node
1427  36 SequenceI seq = annot.sequenceRef;
1428  36 node.setElement(seq);
1429  36 node.setName(seq.getName());
1430   
1431    // Get annotation provider name from the annotation description
1432  36 String ssAnnotProvider = AlignmentAnnotationUtils
1433    .extractSSSourceFromAnnotationDescription(annot);
1434  36 node.setLabel(ssAnnotProvider);
1435  36 node.setAlignmentAnnotation(annot);
1436   
1437    // Initialise AnnotationDetails if present for the annotation
1438  36 if (annot.hasAnnotationDetailsProperty())
1439    {
1440  17 node.setAnnotationDetails(annot.getAnnotationDetailsProperty());
1441    }
1442   
1443    }
1444  28 else if (element instanceof SequenceI)
1445    {
1446    //If annotation is absent, set the default label
1447  28 node.setLabel(Constants.STRUCTURE_PROVIDERS.get("None"));
1448    }
1449    });
1450    }
1451    }