Merge branch 'develop' into features/JAL-2393customMatrices
[jalview.git] / src / jalview / gui / CalculationChooser.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.gui;
22
23 import jalview.analysis.TreeBuilder;
24 import jalview.analysis.scoremodels.ScoreModels;
25 import jalview.analysis.scoremodels.SimilarityParams;
26 import jalview.api.analysis.ScoreModelI;
27 import jalview.api.analysis.SimilarityParamsI;
28 import jalview.util.MessageManager;
29
30 import java.awt.Color;
31 import java.awt.Component;
32 import java.awt.FlowLayout;
33 import java.awt.Font;
34 import java.awt.GridLayout;
35 import java.awt.event.ActionEvent;
36 import java.awt.event.ItemEvent;
37 import java.awt.event.ItemListener;
38 import java.awt.event.MouseAdapter;
39 import java.awt.event.MouseEvent;
40 import java.beans.PropertyVetoException;
41 import java.util.ArrayList;
42 import java.util.List;
43
44 import javax.swing.ButtonGroup;
45 import javax.swing.JButton;
46 import javax.swing.JCheckBox;
47 import javax.swing.JComboBox;
48 import javax.swing.JInternalFrame;
49 import javax.swing.JLabel;
50 import javax.swing.JLayeredPane;
51 import javax.swing.JPanel;
52 import javax.swing.JRadioButton;
53
54 /**
55  * A dialog where a user can choose and action Tree or PCA calculation options
56  */
57 public class CalculationChooser extends JPanel
58 {
59   /*
60    * flag for whether gap matches residue in the PID calculation for a Tree
61    * - true gives Jalview 2.10.1 behaviour
62    * - set to false (using Groovy) for a more correct tree
63    * (JAL-374)
64    */
65   private static boolean treeMatchGaps = true;
66
67   private static final Font VERDANA_11PT = new Font("Verdana", 0, 11);
68
69   AlignFrame af;
70
71   JRadioButton pca;
72
73   JRadioButton tree;
74
75   JRadioButton neighbourJoining;
76
77   JRadioButton averageDistance;
78
79   JComboBox<String> modelNames;
80
81   private JInternalFrame frame;
82
83   private ButtonGroup treeTypes;
84
85   private JCheckBox includeGaps;
86
87   private JCheckBox matchGaps;
88
89   private JCheckBox includeGappedColumns;
90
91   private JCheckBox shorterSequence;
92
93   /**
94    * Constructor
95    * 
96    * @param af
97    */
98   public CalculationChooser(AlignFrame alignFrame)
99   {
100     this.af = alignFrame;
101     init();
102   }
103
104   /**
105    * Lays out the panel and adds it to the desktop
106    */
107   void init()
108   {
109     frame = new JInternalFrame();
110     frame.setContentPane(this);
111     this.setBackground(Color.white);
112
113     /*
114      * Layout consists of 4 or 5 panels:
115      * - first with choice of Tree or PCA
116      * - second with choice of tree method NJ or AV
117      * - third with choice of score model
118      * - fourth with score model parameter options [suppressed]
119      * - fifth with OK and Cancel
120      */
121     tree = new JRadioButton(MessageManager.getString("label.tree"));
122     tree.setOpaque(false);
123     pca = new JRadioButton(
124             MessageManager.getString("label.principal_component_analysis"));
125     pca.setOpaque(false);
126     neighbourJoining = new JRadioButton(
127             MessageManager.getString("label.tree_calc_nj"));
128     averageDistance = new JRadioButton(
129             MessageManager.getString("label.tree_calc_av"));
130     ItemListener listener = new ItemListener()
131     {
132       @Override
133       public void itemStateChanged(ItemEvent e)
134       {
135         neighbourJoining.setEnabled(tree.isSelected());
136         averageDistance.setEnabled(tree.isSelected());
137       }
138     };
139     pca.addItemListener(listener);
140     tree.addItemListener(listener);
141     ButtonGroup calcTypes = new ButtonGroup();
142     calcTypes.add(pca);
143     calcTypes.add(tree);
144     JPanel calcChoicePanel = new JPanel();
145     calcChoicePanel.setOpaque(false);
146     tree.setSelected(true);
147     calcChoicePanel.add(tree);
148     calcChoicePanel.add(pca);
149
150     neighbourJoining.setOpaque(false);
151     treeTypes = new ButtonGroup();
152     treeTypes.add(neighbourJoining);
153     treeTypes.add(averageDistance);
154     neighbourJoining.setSelected(true);
155     JPanel treeChoicePanel = new JPanel();
156     treeChoicePanel.setOpaque(false);
157     treeChoicePanel.add(neighbourJoining);
158     treeChoicePanel.add(averageDistance);
159
160     /*
161      * score models drop-down - with added tooltips!
162      */
163     modelNames = buildModelOptionsList();
164
165     JPanel scoreModelPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
166     scoreModelPanel.setOpaque(false);
167     scoreModelPanel.add(modelNames, FlowLayout.LEFT);
168
169     /*
170      * score model parameters
171      */
172     JPanel paramsPanel = new JPanel(new GridLayout(5, 1));
173     paramsPanel.setOpaque(false);
174     includeGaps = new JCheckBox("Include gaps");
175     matchGaps = new JCheckBox("Match gaps");
176     includeGappedColumns = new JCheckBox("Include gapped columns");
177     shorterSequence = new JCheckBox("Match on shorter sequence");
178     paramsPanel.add(new JLabel("Pairwise sequence scoring options"));
179     paramsPanel.add(includeGaps);
180     paramsPanel.add(matchGaps);
181     paramsPanel.add(includeGappedColumns);
182     paramsPanel.add(shorterSequence);
183
184     /*
185      * OK / Cancel buttons
186      */
187     JButton ok = new JButton(MessageManager.getString("action.ok"));
188     ok.setFont(VERDANA_11PT);
189     ok.addActionListener(new java.awt.event.ActionListener()
190     {
191       @Override
192       public void actionPerformed(ActionEvent e)
193       {
194         ok_actionPerformed();
195       }
196     });
197     JButton cancel = new JButton(MessageManager.getString("action.cancel"));
198     cancel.setFont(VERDANA_11PT);
199     cancel.addActionListener(new java.awt.event.ActionListener()
200     {
201       @Override
202       public void actionPerformed(ActionEvent e)
203       {
204         cancel_actionPerformed(e);
205       }
206     });
207     JPanel actionPanel = new JPanel();
208     actionPanel.setOpaque(false);
209     actionPanel.add(ok);
210     actionPanel.add(cancel);
211
212     boolean includeParams = false;
213     this.add(calcChoicePanel);
214     this.add(treeChoicePanel);
215     this.add(scoreModelPanel);
216     if (includeParams)
217     {
218       this.add(paramsPanel);
219     }
220     this.add(actionPanel);
221
222     int width = 350;
223     int height = includeParams ? 400 : 220;
224     Desktop.addInternalFrame(frame,
225             MessageManager.getString("label.choose_calculation"), width,
226             height, false);
227
228     frame.setLayer(JLayeredPane.PALETTE_LAYER);
229   }
230
231   /**
232    * A rather elaborate helper method (blame Swing, not me) that builds a
233    * drop-down list of score models (by name) with descriptions as tooltips.
234    * There is also a tooltip shown for the currently selected item when hovering
235    * over it (without opening the list).
236    */
237   protected JComboBox<String> buildModelOptionsList()
238   {
239     final JComboBox<String> comboBox = new JComboBox<String>();
240     ComboBoxTooltipRenderer renderer = new ComboBoxTooltipRenderer();
241     comboBox.setRenderer(renderer);
242     final List<String> tips = new ArrayList<String>();
243
244     /*
245      * show tooltip on mouse over the combobox
246      * note the listener has to be on the components that make up
247      * the combobox, doesn't work if just on the combobox
248      */
249     MouseAdapter mouseListener = new MouseAdapter()
250     {
251       @Override
252       public void mouseEntered(MouseEvent e)
253       {
254         comboBox.setToolTipText(tips.get(comboBox.getSelectedIndex()));
255       }
256
257       @Override
258       public void mouseExited(MouseEvent e)
259       {
260         comboBox.setToolTipText(null);
261       }
262     };
263     for (Component c : comboBox.getComponents())
264     {
265       c.addMouseListener(mouseListener);
266     }
267
268     /*
269      * now we can actually add entries to the combobox,
270      * remembering their descriptions for tooltips
271      */
272     ScoreModels scoreModels = ScoreModels.getInstance();
273     for (ScoreModelI sm : scoreModels.getModels())
274     {
275       boolean nucleotide = af.getViewport().getAlignment().isNucleotide();
276       if (sm.isDNA() && nucleotide || sm.isProtein() && !nucleotide)
277       {
278         comboBox.addItem(sm.getName());
279
280         /*
281          * tooltip is description if provided, else text lookup with
282          * fallback on the model name
283          */
284         String tooltip = sm.getDescription();
285         if (tooltip == null)
286         {
287           tooltip = MessageManager.getStringOrReturn("label.score_model_",
288                   sm.getName());
289         }
290         tips.add(tooltip);
291       }
292
293       /*
294        * set the list of tooltips on the combobox's renderer
295        */
296       renderer.setTooltips(tips);
297     }
298
299     return comboBox;
300   }
301
302   /**
303    * Open and calculate the selected tree on 'OK'
304    */
305   protected void ok_actionPerformed()
306   {
307     boolean doPCA = pca.isSelected();
308     ScoreModelI sm = ScoreModels.getInstance().forName(
309             modelNames.getSelectedItem().toString());
310     SimilarityParamsI params = getSimilarityParameters(doPCA);
311
312     if (doPCA)
313     {
314       openPcaPanel(sm, params);
315     }
316     else
317     {
318       openTreePanel(sm, params);
319     }
320
321     // closeFrame();
322   }
323
324   /**
325    * Open a new Tree panel on the desktop
326    * 
327    * @param sm
328    * @param params
329    */
330   protected void openTreePanel(ScoreModelI sm, SimilarityParamsI params)
331   {
332     String treeType = neighbourJoining.isSelected() ? TreeBuilder.NEIGHBOUR_JOINING
333             : TreeBuilder.AVERAGE_DISTANCE;
334     af.newTreePanel(treeType, sm, params);
335   }
336
337   /**
338    * Open a new PCA panel on the desktop
339    * 
340    * @param sm
341    * @param params
342    */
343   protected void openPcaPanel(ScoreModelI sm, SimilarityParamsI params)
344   {
345     AlignViewport viewport = af.getViewport();
346     if (((viewport.getSelectionGroup() != null)
347             && (viewport.getSelectionGroup().getSize() < 4) && (viewport
348             .getSelectionGroup().getSize() > 0))
349             || (viewport.getAlignment().getHeight() < 4))
350     {
351       JvOptionPane
352               .showInternalMessageDialog(
353                       this,
354                       MessageManager
355                               .getString("label.principal_component_analysis_must_take_least_four_input_sequences"),
356                       MessageManager
357                               .getString("label.sequence_selection_insufficient"),
358                       JvOptionPane.WARNING_MESSAGE);
359       return;
360     }
361     new PCAPanel(af.alignPanel, sm, params);
362   }
363
364   /**
365    * 
366    */
367   protected void closeFrame()
368   {
369     try
370     {
371       frame.setClosed(true);
372     } catch (PropertyVetoException ex)
373     {
374     }
375   }
376
377   /**
378    * Returns a data bean holding parameters for similarity (or distance) model
379    * calculation
380    * 
381    * @param doPCA
382    * @return
383    */
384   protected SimilarityParamsI getSimilarityParameters(boolean doPCA)
385   {
386     // commented out: parameter choices read from gui widgets
387     // SimilarityParamsI params = new SimilarityParams(
388     // includeGappedColumns.isSelected(), matchGaps.isSelected(),
389     // includeGaps.isSelected(), shorterSequence.isSelected());
390
391     boolean includeGapGap = true;
392     boolean includeGapResidue = true;
393     boolean matchOnShortestLength = false;
394
395     /*
396      * 'matchGaps' flag is only used in the PID calculation
397      * - set to false for PCA so that PCA using PID reproduces SeqSpace PCA
398      * - set to true for Tree to reproduce Jalview 2.10.1 calculation
399      * - set to false for Tree for a more correct calculation (JAL-374)
400      */
401     boolean matchGap = doPCA ? false : treeMatchGaps;
402
403     return new SimilarityParams(includeGapGap, matchGap, includeGapResidue, matchOnShortestLength);
404   }
405
406   /**
407    * Closes dialog on cancel
408    * 
409    * @param e
410    */
411   protected void cancel_actionPerformed(ActionEvent e)
412   {
413     try
414     {
415       frame.setClosed(true);
416     } catch (Exception ex)
417     {
418     }
419   }
420 }