Clover icon

Coverage Report

  1. Project Clover database Tue Mar 10 2026 14:58:44 GMT
  2. Package jalview.gui

File CalculationChooser.java

 

Coverage histogram

../../img/srcFileCovDistChart0.png
0% of files have more coverage

Code metrics

94
327
33
1
1,198
750
123
0.38
9.91
33
3.73

Classes

Class Line # Actions
CalculationChooser 81 327 123
0.00%
 

Contributing tests

No tests hitting this source file were found.

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.Color;
25    import java.awt.Component;
26    import java.awt.Dimension;
27    import java.awt.FlowLayout;
28    import java.awt.Font;
29    import java.awt.GridLayout;
30    import java.awt.Insets;
31    import java.awt.event.ActionEvent;
32    import java.awt.event.ActionListener;
33    import java.awt.event.FocusEvent;
34    import java.awt.event.FocusListener;
35    import java.awt.event.MouseAdapter;
36    import java.awt.event.MouseEvent;
37    import java.beans.PropertyVetoException;
38    import java.util.ArrayList;
39    import java.util.Collections;
40    import java.util.List;
41   
42    import javax.swing.BorderFactory;
43    import javax.swing.BoxLayout;
44    import javax.swing.ButtonGroup;
45    import javax.swing.DefaultComboBoxModel;
46    import javax.swing.JButton;
47    import javax.swing.JCheckBox;
48    import javax.swing.JComboBox;
49    import javax.swing.JInternalFrame;
50    import javax.swing.JLabel;
51    import javax.swing.JLayeredPane;
52    import javax.swing.JPanel;
53    import javax.swing.JRadioButton;
54    import javax.swing.event.InternalFrameAdapter;
55    import javax.swing.event.InternalFrameEvent;
56   
57    import jalview.analysis.AlignmentUtils;
58    import jalview.analysis.TreeBuilder;
59    import jalview.analysis.scoremodels.ScoreMatrix;
60    import jalview.analysis.scoremodels.ScoreModels;
61    import jalview.analysis.scoremodels.SimilarityParams;
62    import jalview.analysis.scoremodels.ValueAnnotationDistanceModel;
63    import jalview.api.AlignViewportI;
64    import jalview.api.analysis.ScoreModelI;
65    import jalview.api.analysis.SimilarityParamsI;
66    import jalview.bin.Cache;
67    import jalview.datamodel.AlignmentAnnotation;
68    import jalview.datamodel.SequenceGroup;
69    import jalview.datamodel.SequenceI;
70    import jalview.util.MessageManager;
71    import jalview.util.Platform;
72   
73    /**
74    * A dialog where a user can choose and action Tree or PCA calculation options.
75    *
76    * Allows also for dialog-free static methods openPCAPanel(...) and
77    * openTreePanel(...) for scripted use.
78    *
79    */
80    @SuppressWarnings("serial")
 
81    public class CalculationChooser extends JPanel
82    {
83    /*
84    * flag for whether gap matches residue in the PID calculation for a Tree
85    * - true gives Jalview 2.10.1 behaviour
86    * - set to false (using Groovy) for a more correct tree
87    * (JAL-374)
88    */
89    private static boolean treeMatchGaps = true;
90   
91    private static Font VERDANA_11PT;
92   
93    private static final int MIN_PAIRWISE_SELECTION = 2;
94   
95    private static final int MIN_TREE_SELECTION = 3;
96   
97    private static final int MIN_PCA_SELECTION = 4;
98   
99    /**
100    * the name shown in the gui for the secondary structure model. Update by
101    * calling initialiseSecondaryStructureModelName
102    */
103    private static String secondaryStructureModelName;
104   
 
105  0 toggle private void initialiseSecondaryStructureModelName()
106    {
107   
108  0 ScoreModels scoreModels = ScoreModels.getInstance();
109  0 for (ScoreModelI sm : scoreModels.getModels())
110    {
111  0 if (sm.isSecondaryStructure())
112    {
113  0 secondaryStructureModelName = sm.getName();
114    }
115    }
116   
117    }
118   
119    /**
120    * minimum number of sequences needed for PASIMAP is 9 (so each has 8
121    * connections)
122    */
123    private static final int MIN_PASIMAP_SELECTION = 9;
124   
125    AlignFrame af;
126   
127    JRadioButton pairwise;
128   
129    JRadioButton pca;
130   
131    JRadioButton pasimap;
132   
133    JRadioButton neighbourJoining;
134   
135    JRadioButton averageDistance;
136   
137    JComboBox<String> modelNames;
138   
139    JComboBox<String> ssSourceDropdown;
140   
141    JCheckBox excludeSeqWithoutAnnot;
142   
143    JButton calculate;
144   
145    private JInternalFrame frame;
146   
147    private JCheckBox includeGaps;
148   
149    private JCheckBox matchGaps;
150   
151    private JCheckBox includeGappedColumns;
152   
153    private JCheckBox shorterSequence;
154   
155    private static ComboBoxTooltipRenderer renderer; // BH was not static
156   
157    List<String> tips = new ArrayList<>();
158   
159    /*
160    * the most recently opened PCA results panel
161    */
162    private PCAPanel pcaPanel;
163   
164    private PaSiMapPanel pasimapPanel;
165   
166    /**
167    * Open a new Tree panel on the desktop statically. Params are standard (not
168    * set by Groovy). No dialog is opened.
169    *
170    * @param af
171    * @param treeType
172    * @param modelName
173    * @return null if successful; the string
174    * "label.you_need_at_least_n_sequences" if number of sequences
175    * selected is inappropriate
176    */
 
177  0 toggle public static Object openTreePanel(AlignFrame af, String treeType,
178    String modelName)
179    {
180  0 return openTreePanel(af, treeType, modelName, null);
181    }
182   
183    /**
184    * public static method for JalviewJS API to open a PCAPanel without
185    * necessarily using a dialog.
186    *
187    * @param af
188    * @param modelName
189    * @return the PCAPanel, or the string "label.you_need_at_least_n_sequences"
190    * if number of sequences selected is inappropriate
191    */
 
192  0 toggle public static Object openPcaPanel(AlignFrame af, String modelName)
193    {
194  0 return openPcaPanel(af, modelName, null);
195    }
196   
197    /**
198    * Constructor
199    *
200    * @param af
201    */
 
202  0 toggle public CalculationChooser(AlignFrame alignFrame)
203    {
204  0 this.af = alignFrame;
205  0 init();
206  0 af.alignPanel.setCalculationDialog(this);
207   
208    }
209   
210    /**
211    * Lays out the panel and adds it to the desktop
212    */
 
213  0 toggle void init()
214    {
215  0 initialiseSecondaryStructureModelName();
216  0 setLayout(new BorderLayout());
217  0 frame = new JInternalFrame();
218  0 frame.setFrameIcon(null);
219  0 frame.setContentPane(this);
220  0 this.setBackground(Color.white);
221  0 frame.addFocusListener(new FocusListener()
222    {
223   
 
224  0 toggle @Override
225    public void focusLost(FocusEvent e)
226    {
227    }
228   
 
229  0 toggle @Override
230    public void focusGained(FocusEvent e)
231    {
232  0 validateCalcTypes();
233    }
234    });
235    /*
236    * Layout consists of 3 or 4 panels:
237    * - first with choice of PCA or tree method NJ or AV
238    * - second with choice of score model
239    * - third with score model parameter options [suppressed]
240    * - fourth with OK and Cancel
241    */
242  0 pca = new JRadioButton(
243    MessageManager.getString("label.principal_component_analysis"));
244  0 pca.setOpaque(false);
245   
246  0 pasimap = new JRadioButton( // create the JRadioButton for pasimap with
247    // label.pasimap as its text
248    MessageManager.getString("label.pasimap"));
249  0 pasimap.setOpaque(false);
250   
251  0 neighbourJoining = new JRadioButton(
252    MessageManager.getString("label.tree_calc_nj"));
253  0 neighbourJoining.setSelected(true);
254  0 neighbourJoining.setOpaque(false);
255   
256  0 averageDistance = new JRadioButton(
257    MessageManager.getString("label.tree_calc_av"));
258  0 averageDistance.setOpaque(false);
259   
260  0 pairwise = new JRadioButton(
261    MessageManager.getString("action.pairwise_alignment"));
262  0 pairwise.setOpaque(false);
263   
264  0 JPanel calcChoicePanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
265  0 calcChoicePanel.setOpaque(false);
266   
267    // first create the Tree calculation's border panel
268  0 JPanel treePanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
269  0 treePanel.setOpaque(false);
270   
271  0 JvSwingUtils.createTitledBorder(treePanel,
272    MessageManager.getString("label.tree"), true);
273   
274    // then copy the inset dimensions for the border-less PCA panel
275  0 JPanel pcaBorderless = new JPanel(new FlowLayout(FlowLayout.LEFT));
276  0 Insets b = treePanel.getBorder().getBorderInsets(treePanel);
277  0 pcaBorderless.setBorder(
278    BorderFactory.createEmptyBorder(2, b.left, 2, b.right));
279  0 pcaBorderless.setOpaque(false);
280   
281  0 pcaBorderless.add(pca, FlowLayout.LEFT);
282  0 calcChoicePanel.add(pcaBorderless, FlowLayout.LEFT);
283   
284    // create pasimap panel
285  0 JPanel pasimapBorderless = new JPanel(new FlowLayout(FlowLayout.LEFT)); // create
286    // new
287    // JPanel
288    // (button)
289    // for
290    // pasimap
291  0 pasimapBorderless.setBorder(
292    BorderFactory.createEmptyBorder(2, b.left, 2, b.right)); // set
293    // border
294    // (margin)
295    // for
296    // button
297    // (same as
298    // treePanel
299    // and pca)
300  0 pasimapBorderless.setOpaque(false); // false -> stops every pixel inside
301    // border from being painted
302  0 pasimapBorderless.add(pasimap, FlowLayout.LEFT); // add pasimap button to
303    // the JPanel
304  0 if (!Platform.isJS())
305    {
306    // FIXME JAL-4443
307  0 calcChoicePanel.add(pasimapBorderless, FlowLayout.LEFT); // add button
308    // with
309    // border and
310    // everything to
311    // the overall
312    // ChoicePanel
313    }
314   
315  0 treePanel.add(neighbourJoining);
316  0 treePanel.add(averageDistance);
317   
318  0 calcChoicePanel.add(treePanel);
319  0 calcChoicePanel.add(pairwise, FlowLayout.CENTER);
320   
321  0 ButtonGroup calcTypes = new ButtonGroup();
322  0 calcTypes.add(pca);
323  0 if (!Platform.isJS())
324    {
325    // FIXME JAL-4443
326  0 calcTypes.add(pasimap);
327    }
328  0 calcTypes.add(neighbourJoining);
329  0 calcTypes.add(averageDistance);
330  0 calcTypes.add(pairwise);
331   
332  0 ActionListener calcChanged = new ActionListener()
333    {
 
334  0 toggle @Override
335    public void actionPerformed(ActionEvent e)
336    {
337  0 validateCalcTypes();
338    }
339    };
340  0 pca.addActionListener(calcChanged);
341  0 pasimap.addActionListener(calcChanged); // add the calcChanged
342    // ActionListener to pasimap -->
343    // <++> idk
344  0 neighbourJoining.addActionListener(calcChanged);
345  0 averageDistance.addActionListener(calcChanged);
346   
347    // to do
348  0 ssSourceDropdown = buildSSSourcesOptionsList();
349  0 ssSourceDropdown.setVisible(false); // Initially hide the dropdown
350   
351  0 excludeSeqWithoutAnnot = new JCheckBox("Exclude sequences without annotation");
352  0 excludeSeqWithoutAnnot.setVisible(false); // Initially hide the checkbox
353   
354  0 pairwise.addActionListener(calcChanged);
355    /*
356    * score models drop-down - with added tooltips!
357    */
358  0 modelNames = buildModelOptionsList();
359   
360    // Step 3: Show or Hide Dropdown Based on Selection
361  0 modelNames.addActionListener(new ActionListener()
362    {
 
363  0 toggle @Override
364    public void actionPerformed(ActionEvent e)
365    {
366  0 String selectedModel = modelNames.getSelectedItem().toString();
367   
368  0 if (selectedModel.equals(secondaryStructureModelName))
369    {
370  0 ssSourceDropdown.setVisible(true);
371  0 excludeSeqWithoutAnnot.setVisible(true);
372    }
373    else
374    {
375  0 ssSourceDropdown.setVisible(false);
376  0 excludeSeqWithoutAnnot.setVisible(false);
377    }
378    }
379    });
380   
381    /*
382    * score model panel
383    */
384   
385  0 JPanel scoreModelPanel = new JPanel();
386  0 scoreModelPanel.setOpaque(false);
387  0 scoreModelPanel.setLayout(new BoxLayout(scoreModelPanel, BoxLayout.Y_AXIS));
388   
389    // First row
390  0 JPanel topRow = new JPanel(new FlowLayout(FlowLayout.CENTER));
391  0 topRow.setOpaque(false);
392  0 topRow.add(modelNames);
393  0 topRow.add(ssSourceDropdown);
394   
395    // Second row
396  0 JPanel bottomRow = new JPanel(new FlowLayout(FlowLayout.LEFT));
397  0 bottomRow.setOpaque(false);
398  0 bottomRow.add(excludeSeqWithoutAnnot);
399   
400    // Add rows to main panel
401  0 scoreModelPanel.add(topRow);
402  0 scoreModelPanel.add(bottomRow);
403   
404    /*
405    * score model parameters
406    */
407  0 JPanel paramsPanel = new JPanel(new GridLayout(5, 1));
408  0 paramsPanel.setOpaque(false);
409  0 includeGaps = new JCheckBox("Include gaps");
410  0 matchGaps = new JCheckBox("Match gaps");
411  0 includeGappedColumns = new JCheckBox("Include gapped columns");
412  0 shorterSequence = new JCheckBox("Match on shorter sequence");
413  0 paramsPanel.add(new JLabel("Pairwise sequence scoring options"));
414  0 paramsPanel.add(includeGaps);
415  0 paramsPanel.add(matchGaps);
416  0 paramsPanel.add(includeGappedColumns);
417  0 paramsPanel.add(shorterSequence);
418   
419  0 if (VERDANA_11PT == null)
420    {
421  0 VERDANA_11PT = new Font("Verdana", 0, 11);
422    }
423    /*
424    * OK / Cancel buttons
425    */
426  0 calculate = new JButton(MessageManager.getString("action.calculate"));
427  0 calculate.setFont(VERDANA_11PT);
428  0 calculate.addActionListener(new java.awt.event.ActionListener()
429    {
 
430  0 toggle @Override
431    public void actionPerformed(ActionEvent e)
432    {
433  0 calculate_actionPerformed();
434    }
435    });
436  0 JButton close = new JButton(MessageManager.getString("action.close"));
437  0 close.setFont(VERDANA_11PT);
438  0 close.addActionListener(new java.awt.event.ActionListener()
439    {
 
440  0 toggle @Override
441    public void actionPerformed(ActionEvent e)
442    {
443  0 close_actionPerformed();
444    }
445    });
446  0 JPanel actionPanel = new JPanel();
447  0 actionPanel.setOpaque(false);
448  0 actionPanel.add(calculate);
449  0 actionPanel.add(close);
450   
451  0 boolean includeParams = false;
452  0 this.add(calcChoicePanel, BorderLayout.CENTER);
453  0 calcChoicePanel.add(scoreModelPanel);
454  0 if (includeParams)
455    {
456  0 scoreModelPanel.add(paramsPanel);
457    }
458  0 this.add(actionPanel, BorderLayout.SOUTH);
459   
460  0 int width = 375;
461  0 int height = includeParams ? 420 : 275;
462   
463  0 setMinimumSize(new Dimension(325, height - 10));
464  0 String title = MessageManager.getString("label.choose_calculation");
465  0 if (af.getViewport().getViewName() != null)
466    {
467  0 title = title + " (" + af.getViewport().getViewName() + ")";
468    }
469   
470  0 Desktop.addInternalFrame(frame, title, Desktop.FRAME_MAKE_VISIBLE, width, height, Desktop.FRAME_NOT_RESIZABLE, Desktop.FRAME_SET_MIN_SIZE_300);
471  0 calcChoicePanel.doLayout();
472  0 revalidate();
473    /*
474    * null the AlignmentPanel's reference to the dialog when it is closed
475    */
476  0 frame.addInternalFrameListener(new InternalFrameAdapter()
477    {
 
478  0 toggle @Override
479    public void internalFrameClosed(InternalFrameEvent evt)
480    {
481  0 af.alignPanel.setCalculationDialog(null);
482    };
483    });
484   
485  0 validateCalcTypes();
486  0 frame.setLayer(JLayeredPane.PALETTE_LAYER);
487    }
488   
489    /**
490    * enable calculations applicable for the current alignment or selection.
491    */
 
492  0 toggle protected void validateCalcTypes()
493    {
494  0 int size = af.getViewport().getAlignment().getHeight();
495  0 if (af.getViewport().getSelectionGroup() != null)
496    {
497  0 size = af.getViewport().getSelectionGroup().getSize();
498    }
499   
500    /*
501    * disable calc options for which there is insufficient input data
502    * return value of true means enabled and selected
503    */
504  0 int minPca = 1; //af.getViewport().getAlignment()
505    //.isSecondaryStructurePresent() ? 1 : MIN_PCA_SELECTION;
506  0 boolean checkPca = checkEnabled(pca, size, minPca);
507   
508  0 boolean checkPasimap = checkEnabled(pasimap, size,
509    MIN_PASIMAP_SELECTION); // check if pasimap is enabled and min_size
510    // is fulfilled
511  0 int minTree = 1; //af.getViewport().getAlignment()
512    //.isSecondaryStructurePresent() ? 1 : MIN_TREE_SELECTION;
513  0 boolean checkNeighbourJoining = checkEnabled(neighbourJoining, size,
514    minTree);
515  0 boolean checkAverageDistance = checkEnabled(averageDistance, size,
516    minTree);
517  0 boolean checkPairwise = checkEnabled(pairwise, size,
518    MIN_PAIRWISE_SELECTION);
519   
520  0 if (checkPca || checkPasimap || checkPca || checkNeighbourJoining
521    || checkAverageDistance || checkPairwise)
522    {
523  0 calculate.setToolTipText(null);
524  0 calculate.setEnabled(true);
525    }
526    else
527    {
528  0 calculate.setEnabled(false);
529    }
530  0 updateScoreModels(modelNames, tips);
531    }
532   
533    /**
534    * Check the input and disable a calculation's radio button if necessary. A
535    * tooltip is shown for disabled calculations.
536    *
537    * @param calc
538    * - radio button for the calculation being validated
539    * @param size
540    * - size of input to calculation
541    * @param minsize
542    * - minimum size for calculation
543    * @return true if size >= minsize and calc.isSelected
544    */
 
545  0 toggle private boolean checkEnabled(JRadioButton calc, int size, int minsize)
546    {
547  0 String ttip = MessageManager
548    .formatMessage("label.you_need_at_least_n_sequences", minsize);
549   
550  0 calc.setEnabled(size >= minsize);
551  0 if (!calc.isEnabled())
552    {
553  0 calc.setToolTipText(ttip);
554    }
555    else
556    {
557  0 calc.setToolTipText(null);
558    }
559  0 if (calc.isSelected())
560    {
561  0 modelNames.setEnabled(calc.isEnabled());
562  0 if (calc.isEnabled())
563    {
564  0 return true;
565    }
566    else
567    {
568  0 calculate.setToolTipText(ttip);
569    }
570    }
571  0 return false;
572    }
573   
574    /**
575    * A rather elaborate helper method (blame Swing, not me) that builds a
576    * drop-down list of score models (by name) with descriptions as tooltips.
577    * There is also a tooltip shown for the currently selected item when hovering
578    * over it (without opening the list).
579    */
 
580  0 toggle protected JComboBox<String> buildModelOptionsList()
581    {
582  0 JComboBox<String> scoreModelsCombo = new JComboBox<>();
583  0 if (renderer == null)
584    {
585  0 renderer = new ComboBoxTooltipRenderer();
586    }
587  0 scoreModelsCombo.setRenderer(renderer);
588   
589    /*
590    * show tooltip on mouse over the combobox
591    * note the listener has to be on the components that make up
592    * the combobox, doesn't work if just on the combobox
593    */
594  0 final MouseAdapter mouseListener = new MouseAdapter()
595    {
 
596  0 toggle @Override
597    public void mouseEntered(MouseEvent e)
598    {
599  0 scoreModelsCombo.setToolTipText(
600    tips.get(scoreModelsCombo.getSelectedIndex()));
601    }
602   
 
603  0 toggle @Override
604    public void mouseExited(MouseEvent e)
605    {
606  0 scoreModelsCombo.setToolTipText(null);
607    }
608    };
609  0 for (Component c : scoreModelsCombo.getComponents())
610    {
611  0 c.addMouseListener(mouseListener);
612    }
613   
614  0 updateScoreModels(scoreModelsCombo, tips);
615   
616    /*
617    * set the list of tooltips on the combobox's renderer
618    */
619  0 renderer.setTooltips(tips);
620   
621  0 return scoreModelsCombo;
622    }
623   
 
624  0 toggle private JComboBox<String> buildSSSourcesOptionsList()
625    {
626  0 final JComboBox<String> comboBox = new JComboBox<>();
627  0 Object curSel = comboBox.getSelectedItem();
628  0 DefaultComboBoxModel<String> sourcesModel = new DefaultComboBoxModel<>();
629   
630  0 List<String> ssSources = getApplicableSecondaryStructureSources();
631   
632  0 if (ssSources == null)
633    {
634  0 return comboBox;
635    }
636  0 ssSources.add(0, MessageManager.getString("option.ss_providers_all"));
637   
638  0 boolean selectedIsPresent = false;
639  0 for (String source : ssSources)
640    {
641  0 if (curSel != null && source.equals(curSel))
642    {
643  0 selectedIsPresent = true;
644  0 curSel = source;
645    }
646  0 sourcesModel.addElement(source);
647   
648    }
649   
650  0 if (selectedIsPresent)
651    {
652  0 sourcesModel.setSelectedItem(curSel);
653    }
654  0 comboBox.setModel(sourcesModel);
655   
656  0 return comboBox;
657    }
658   
 
659  0 toggle private void updateScoreModels(JComboBox<String> comboBox,
660    List<String> toolTips)
661    {
662  0 Object curSel = comboBox.getSelectedItem();
663  0 toolTips.clear();
664  0 DefaultComboBoxModel<String> model = new DefaultComboBoxModel<>();
665   
666    /*
667    * select the score models applicable to the alignment type
668    */
669  0 boolean nucleotide = af.getViewport().getAlignment().isNucleotide();
670   
671  0 boolean ssPresent = af.getViewport().getAlignment()
672    .isSecondaryStructurePresent();
673   
674  0 List<ScoreModelI> models = getApplicableScoreModels(nucleotide,
675    pca.isSelected(), ssPresent,
676    (pasimap.isSelected() || pairwise.isSelected()));
677   
678    /*
679    * now we can actually add entries to the combobox,
680    * remembering their descriptions for tooltips
681    */
682  0 boolean selectedIsPresent = false;
683  0 for (ScoreModelI sm : models)
684    {
685  0 if (curSel != null && sm.getName().equals(curSel))
686    {
687  0 selectedIsPresent = true;
688  0 curSel = sm.getName();
689    }
690  0 model.addElement(sm.getName());
691   
692    /*
693    * tooltip is description if provided, else text lookup with
694    * fallback on the model name
695    */
696  0 String tooltip = sm.getDescription();
697  0 if (tooltip == null)
698    {
699  0 tooltip = MessageManager.getStringOrReturn("label.score_model_",
700    sm.getName());
701    }
702  0 toolTips.add(tooltip);
703    }
704   
705  0 if (selectedIsPresent)
706    {
707  0 model.setSelectedItem(curSel);
708    }
709    // finally, update the model
710  0 comboBox.setModel(model);
711  0 comboBox.setEnabled(model.getSize() > 0);
712   
713    }
714   
715    /**
716    * Builds a list of score models which are applicable for the alignment and
717    * calculation type (peptide or generic models for protein, nucleotide or
718    * generic models for nucleotide).
719    * <p>
720    * As a special case, includes BLOSUM62 as an extra option for nucleotide PCA.
721    * This is for backwards compatibility with Jalview prior to 2.8 when BLOSUM62
722    * was the only score matrix supported. This is included if property
723    * BLOSUM62_PCA_FOR_NUCLEOTIDE is set to true in the Jalview properties file.
724    *
725    * @param nucleotide
726    * @param forPca
727    * @param ssPresent
728    * - include secondary structure similarity model
729    * @param forPasimap
730    * - limit to ScoreMatrix based models - allows use of AlignSeq
731    * @return
732    */
 
733  0 toggle protected static List<ScoreModelI> getApplicableScoreModels(
734    boolean nucleotide, boolean forPca, boolean ssPresent,
735    boolean forPasimap)
736    {
737  0 List<ScoreModelI> filtered = new ArrayList<>();
738   
739  0 ScoreModels scoreModels = ScoreModels.getInstance();
740  0 for (ScoreModelI sm : scoreModels.getModels())
741    {
742  0 if ((!forPasimap || sm instanceof ScoreMatrix)
743    && (!nucleotide && sm.isProtein() || nucleotide && sm.isDNA()
744    || sm.isSecondaryStructure() && ssPresent || sm instanceof ValueAnnotationDistanceModel))
745   
746    {
747  0 filtered.add(sm);
748    }
749    }
750   
751    /*
752    * special case: add BLOSUM62 as last option for nucleotide PCA,
753    * for backwards compatibility with Jalview < 2.8 (JAL-2962)
754    */
755  0 if (!forPasimap && nucleotide && forPca
756    && Cache.getDefault(Preferences.BLOSUM62_PCA_FOR_NUCLEOTIDE,
757    false))
758    {
759  0 filtered.add(scoreModels.getBlosum62());
760    }
761   
762  0 return filtered;
763    }
764   
765    /*
766    * This method returns the secondary structure sources of the
767    * alignment or selection group
768    */
 
769  0 toggle protected List<String> getApplicableSecondaryStructureSources()
770    {
771  0 AlignmentAnnotation[] annotations = null;
772   
773    // If there is a selection group, then find sources from those
774    // sequences
775  0 if (af.getViewport().getSelectionGroup() != null)
776    {
777  0 SequenceGroup sg = af.getViewport().getSelectionGroup();
778  0 List<SequenceI> sequences = sg.getSequences();
779   
780    // Iterate through each sequence to get the annotations
781  0 AlignmentAnnotation[] alignAnnot = new AlignmentAnnotation[0];
782  0 if (sequences != null)
783    {
784  0 List<AlignmentAnnotation> tempList = new ArrayList<>();
785  0 for (SequenceI seq : sequences)
786    {
787  0 AlignmentAnnotation[] seqAnnotations = seq.getAnnotation();
788  0 if (seqAnnotations != null)
789    {
790  0 Collections.addAll(tempList, seqAnnotations);
791    }
792    }
793  0 alignAnnot = tempList.toArray(new AlignmentAnnotation[0]);
794    }
795   
796    // Get list of secondary structure annotations from the list of
797    // annotations
798  0 List<AlignmentAnnotation> ssAnnotations = AlignmentUtils
799    .getSecondaryStructureAnnots(alignAnnot);
800   
801  0 if (ssAnnotations != null)
802    {
803  0 annotations = ssAnnotations.toArray(new AlignmentAnnotation[0]);
804  0 List<String> ssSources = AlignmentUtils
805    .extractSSSourceInAlignmentAnnotation(annotations);
806  0 return ssSources;
807    }
808    }
809   
810    else
811    {
812  0 annotations = af.getViewport().getAlignment()
813    .getAlignmentAnnotation();
814  0 List<String> ssSources = AlignmentUtils
815    .extractSSSourceInAlignmentAnnotation(annotations);
816  0 return ssSources;
817    }
818   
819  0 return null;
820    }
821   
822    /**
823    * Open and calculate the selected tree or PCA on 'OK'
824    */
 
825  0 toggle protected void calculate_actionPerformed()
826    {
827  0 boolean doPCA = pca.isSelected();
828  0 boolean doPaSiMap = pasimap.isSelected();
829  0 boolean doPairwise = pairwise.isSelected();
830  0 String modelName = modelNames.getSelectedItem() == null ? ""
831    : modelNames.getSelectedItem().toString();
832   
833  0 String ssSource = null;
834  0 boolean excludeSeqsWithoutAnnot = false;
835   
836  0 if (modelName.equals(secondaryStructureModelName))
837    {
838  0 Object selectedItem = ssSourceDropdown.getSelectedItem();
839  0 if (selectedItem != null)
840    {
841  0 ssSource = selectedItem.toString();
842    }
843  0 excludeSeqsWithoutAnnot = excludeSeqWithoutAnnot.isSelected();
844    }
845  0 SimilarityParams params = getSimilarityParameters(doPCA);
846  0 params.setSecondaryStructureSource(ssSource);
847  0 params.setExcludeSeqWithoutAnnot(excludeSeqsWithoutAnnot);
848   
849  0 if (doPCA)
850    {
851  0 openPcaPanel(modelName, params);
852    }
853  0 else if (doPaSiMap)
854    {
855  0 openPasimapPanel(modelName, params);
856    }
857  0 else if (doPairwise)
858    {
859  0 openPairwisePanel(modelName, params);
860    }
861    else
862    {
863  0 openTreePanel(modelName, params);
864    }
865   
866  0 closeFrame();
867    }
868   
 
869  0 toggle private void openPairwisePanel(String modelName, SimilarityParamsI params)
870    {
871  0 ScoreModelI sm = ScoreModels.getInstance().getScoreModel(modelName,
872    af.alignPanel);
873  0 if (sm == null || !(sm instanceof ScoreMatrix))
874    {
875  0 return;
876    }
877  0 new Thread(new Runnable()
878    {
 
879  0 toggle @Override
880    public void run()
881    {
882  0 String pairwise_alignment_title = af.formCalculationTitle(
883    MessageManager.getString("action.pairwise_alignment")
884    + " with " + sm.getName(),
885    af.getViewport().getSelectionGroup() != null,
886    af.getTitle());
887  0 JInternalFrame frame = new JInternalFrame();
888  0 frame.setFrameIcon(null);
889  0 frame.setContentPane(
890    new PairwiseAlignPanel(af.getViewport(), (ScoreMatrix) sm));
891  0 Desktop.addInternalFrame(frame, pairwise_alignment_title, 600, 500);
892    }
893    }).start();
894    }
895   
896    /**
897    * Open a new Tree panel on the desktop
898    *
899    * @param modelName
900    * @param params
901    */
 
902  0 toggle protected void openTreePanel(String modelName, SimilarityParamsI params)
903    {
904    /*
905    * gui validation shouldn't allow insufficient sequences here, but leave
906    * this check in in case this method gets exposed programmatically in future
907    */
908  0 AlignViewport viewport = af.getViewport();
909  0 SequenceGroup sg = viewport.getSelectionGroup();
910    // FIXME @RENIA - for 2.12 - score model should do this check, not except on modelName
911  0 boolean isAnnotationBased = modelName.equals(secondaryStructureModelName);
912    // FIXME @RENIA - CURRENTLY WE ALLOW ANY SIZE SELECTION TO BE USED FOR
913    // SECONDARY STRUCTURE SIMILARITY - WE SHOULD INSTEAD CHECK HOW MANY
914    // ANNOTATION ROWS THERE ARE
915  0 if (sg != null && sg.getSize() < MIN_TREE_SELECTION
916    && !isAnnotationBased)
917    {
918  0 JvOptionPane.showMessageDialog(this, // was opening on Desktop?
919    MessageManager.formatMessage(
920    "label.you_need_at_least_n_sequences",
921    MIN_TREE_SELECTION),
922    MessageManager.getString("label.not_enough_sequences"),
923    JvOptionPane.WARNING_MESSAGE);
924  0 return;
925    }
926   
927    // Check min no of annotations are present for tree calculation
928  0 if(false && isAnnotationBased
929    && sg != null
930    && sg.noOfSecondaryStructureAnnots() < MIN_TREE_SELECTION)
931    {
932  0 JvOptionPane.showMessageDialog(Desktop.getInstance(),
933    MessageManager.formatMessage(
934    "label.you_need_at_least_n_annotations",
935    MIN_TREE_SELECTION),
936    MessageManager.getString("label.not_enough_annotations"),
937    JvOptionPane.WARNING_MESSAGE);
938  0 return;
939    }
940   
941  0 if (sg == null && isAnnotationBased) {
942  0 AlignmentAnnotation[] alignAnnots = viewport.getAlignment()
943    .getAlignmentAnnotation();
944  0 List<AlignmentAnnotation> ssAnnots = AlignmentUtils
945    .getSecondaryStructureAnnots(alignAnnots);
946   
947    // Check min no of annotations are present in the alignment for tree
948    // calculation when selection group is null
949  0 if (ssAnnots == null || ssAnnots.size() < MIN_TREE_SELECTION)
950    {
951  0 JvOptionPane.showMessageDialog(Desktop.getInstance(),
952    MessageManager.formatMessage(
953    "label.you_need_seq_with_at_least_n_annotations",
954    MIN_TREE_SELECTION),
955    MessageManager.getString("label.not_enough_annotations"),
956    JvOptionPane.WARNING_MESSAGE);
957  0 return;
958    }
959    }
960  0 String treeType = neighbourJoining.isSelected()
961    ? TreeBuilder.NEIGHBOUR_JOINING
962    : TreeBuilder.AVERAGE_DISTANCE;
963  0 af.newTreePanel(treeType, modelName, params, isAnnotationBased);
964    }
965   
966    /**
967    * Open a new PCA panel on the desktop
968    *
969    * @param modelName
970    * @param params
971    */
 
972  0 toggle protected void openPcaPanel(String modelName, SimilarityParamsI params)
973    {
974  0 Object ret = openPcaPanel(af, modelName, params);
975  0 if (ret instanceof String)
976    {
977  0 JvOptionPane.showInternalMessageDialog(this,
978    MessageManager.formatMessage(
979    (String) ret,
980    MIN_PCA_SELECTION),
981    MessageManager
982    .getString("label.sequence_selection_insufficient"),
983    JvOptionPane.WARNING_MESSAGE);
984    }
985    else
986    {
987    // only used for test suite
988  0 pcaPanel = (PCAPanel) ret;
989    }
990   
991    }
992   
993    /**
994    * Open a new Tree panel on the desktop statically
995    * TODO: allow annotation based trees
996    * @param af
997    * @param treeType
998    * @param modelName
999    * @param params
1000    * @return null, or the string "label.you_need_at_least_n_sequences" if number
1001    * of sequences selected is inappropriate
1002    */
 
1003  0 toggle public static Object openTreePanel(AlignFrame af, String treeType,
1004    String modelName, SimilarityParamsI params)
1005    {
1006   
1007    /*
1008    * gui validation shouldn't allow insufficient sequences here, but leave
1009    * this check in in case this method gets exposed programmatically in future
1010    */
1011  0 AlignViewportI viewport = af.getViewport();
1012  0 SequenceGroup sg = viewport.getSelectionGroup();
1013  0 if (sg != null && sg.getSize() < MIN_TREE_SELECTION)
1014    {
1015  0 return "label.you_need_at_least_n_sequences";
1016    }
1017   
1018  0 if (params == null)
1019    {
1020  0 params = getSimilarityParameters(false);
1021    }
1022   
1023  0 af.newTreePanel(treeType, modelName, params,false);
1024  0 return null;
1025    }
1026   
1027   
1028    /**
1029    * public static method for JalviewJS API
1030    *
1031    * @param af
1032    * @param modelName
1033    * @param params
1034    * @return the PCAPanel, or null if number of sequences selected is
1035    * inappropriate
1036    */
 
1037  0 toggle public static Object openPcaPanel(AlignFrame af, String modelName,
1038    SimilarityParamsI params)
1039    {
1040  0 AlignViewportI viewport = af.getViewport();
1041   
1042  0 SequenceGroup sg = viewport.getSelectionGroup();
1043   
1044  0 boolean isAnnotationBased = modelName.equals(secondaryStructureModelName);
1045   
1046    /*
1047    * gui validation shouldn't allow insufficient sequences here, but leave
1048    * this check in in case this method gets exposed programmatically in future
1049    *
1050    *
1051    */
1052  0 if ((((viewport.getSelectionGroup() != null)
1053    && (viewport.getSelectionGroup().getSize() < MIN_PCA_SELECTION)
1054    && (viewport.getSelectionGroup().getSize() > 0))
1055    || (viewport.getAlignment().getHeight() < MIN_PCA_SELECTION))
1056    && !isAnnotationBased)
1057    {
1058  0 return "label.you_need_at_least_n_sequences";
1059    }
1060   
1061  0 if ( isAnnotationBased
1062    && sg != null
1063    && sg.noOfSecondaryStructureAnnots() < MIN_PCA_SELECTION
1064    && sg.getSize() > 0 )
1065    {
1066  0 return "label.you_need_at_least_n_annotations";
1067    }
1068   
1069  0 AlignmentAnnotation[] alignAnnots = viewport.getAlignment()
1070    .getAlignmentAnnotation();
1071  0 List<AlignmentAnnotation> ssAnnots = AlignmentUtils
1072    .getSecondaryStructureAnnots(alignAnnots);
1073   
1074  0 if ( isAnnotationBased
1075    && sg == null
1076    && (ssAnnots == null || ssAnnots.size() < MIN_PCA_SELECTION)
1077    && viewport.getAlignment().getHeight() < MIN_PCA_SELECTION )
1078    {
1079  0 return "label.you_need_at_least_n_annotations";
1080    }
1081   
1082  0 if (params == null)
1083    {
1084  0 params = getSimilarityParameters(true);
1085    }
1086   
1087    /*
1088    * construct the panel and kick off its calculation thread
1089    */
1090  0 PCAPanel pcap = new PCAPanel(af.alignPanel, modelName, params);
1091  0 new Thread(pcap).start();
1092  0 return pcap;
1093    }
1094   
1095    /**
1096    * Open a new PaSiMap panel on the desktop
1097    *
1098    * @param modelName
1099    * @param params
1100    */
 
1101  0 toggle protected void openPasimapPanel(String modelName,
1102    SimilarityParamsI params)
1103    {
1104  0 AlignViewport viewport = af.getViewport();
1105   
1106    /*
1107    * gui validation shouldn't allow insufficient sequences here, but leave
1108    * this check in in case this method gets exposed programmatically in future
1109    */
1110  0 if (((viewport.getSelectionGroup() != null)
1111    && (viewport.getSelectionGroup()
1112    .getSize() < MIN_PASIMAP_SELECTION)
1113    && (viewport.getSelectionGroup().getSize() > 0))
1114    || (viewport.getAlignment()
1115    .getHeight() < MIN_PASIMAP_SELECTION))
1116    {
1117  0 JvOptionPane.showInternalMessageDialog(this,
1118    MessageManager.formatMessage(
1119    "label.you_need_at_least_n_sequences",
1120    MIN_PASIMAP_SELECTION),
1121    MessageManager
1122    .getString("label.sequence_selection_insufficient"),
1123    JvOptionPane.WARNING_MESSAGE);
1124  0 return;
1125    }
1126   
1127    /*
1128    * construct the panel and kick off its calculation thread
1129    */
1130  0 pasimapPanel = new PaSiMapPanel(af.alignPanel, modelName);
1131  0 new Thread(pasimapPanel).start();
1132   
1133    }
1134   
1135    /**
1136    *
1137    */
 
1138  0 toggle protected void closeFrame()
1139    {
1140  0 try
1141    {
1142  0 frame.setClosed(true);
1143    } catch (PropertyVetoException ex)
1144    {
1145    }
1146    }
1147   
1148   
1149    /**
1150    * Returns a data bean holding parameters for similarity (or distance) model
1151    * calculation
1152    *
1153    * @param doPCA
1154    * @return
1155    */
 
1156  0 toggle public static SimilarityParams getSimilarityParameters(
1157    boolean doPCA)
1158    {
1159    // commented out: parameter choices read from gui widgets
1160    // SimilarityParamsI params = new SimilarityParams(
1161    // includeGappedColumns.isSelected(), matchGaps.isSelected(),
1162    // includeGaps.isSelected(), shorterSequence.isSelected());
1163   
1164  0 boolean includeGapGap = true;
1165  0 boolean includeGapResidue = true;
1166  0 boolean matchOnShortestLength = false;
1167   
1168    /*
1169    * 'matchGaps' flag is only used in the PID calculation
1170    * - set to false for PCA so that PCA using PID reproduces SeqSpace PCA
1171    * - set to true for Tree to reproduce Jalview 2.10.1 calculation
1172    * - set to false for Tree for a more correct calculation (JAL-374)
1173    */
1174  0 boolean matchGap = doPCA ? false : treeMatchGaps;
1175   
1176  0 return new SimilarityParams(includeGapGap, matchGap, includeGapResidue,
1177    matchOnShortestLength);
1178   
1179    }
1180   
1181    /**
1182    * Closes dialog on Close button press
1183    */
 
1184  0 toggle protected void close_actionPerformed()
1185    {
1186  0 try
1187    {
1188  0 frame.setClosed(true);
1189    } catch (Exception ex)
1190    {
1191    }
1192    }
1193   
 
1194  0 toggle public PCAPanel getPcaPanel()
1195    {
1196  0 return pcaPanel;
1197    }
1198    }