Merge branch 'documentation/JAL-2751patch2102b2' into releases/Release_2_10_2b1_Branch
[jalview.git] / src / jalview / gui / FeatureColourChooser.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.api.FeatureColourI;
24 import jalview.datamodel.GraphLine;
25 import jalview.schemes.FeatureColour;
26 import jalview.util.MessageManager;
27
28 import java.awt.BorderLayout;
29 import java.awt.Color;
30 import java.awt.Dimension;
31 import java.awt.FlowLayout;
32 import java.awt.event.ActionEvent;
33 import java.awt.event.ActionListener;
34 import java.awt.event.MouseAdapter;
35 import java.awt.event.MouseEvent;
36
37 import javax.swing.BorderFactory;
38 import javax.swing.JCheckBox;
39 import javax.swing.JColorChooser;
40 import javax.swing.JComboBox;
41 import javax.swing.JLabel;
42 import javax.swing.JPanel;
43 import javax.swing.JSlider;
44 import javax.swing.JTextField;
45 import javax.swing.border.LineBorder;
46 import javax.swing.event.ChangeEvent;
47 import javax.swing.event.ChangeListener;
48
49 public class FeatureColourChooser extends JalviewDialog
50 {
51   // FeatureSettings fs;
52   private FeatureRenderer fr;
53
54   private FeatureColourI cs;
55
56   private FeatureColourI oldcs;
57
58   private AlignmentPanel ap;
59
60   private boolean adjusting = false;
61
62   final private float min;
63
64   final private float max;
65
66   final private float scaleFactor;
67
68   private String type = null;
69
70   private JPanel minColour = new JPanel();
71
72   private JPanel maxColour = new JPanel();
73
74   private JComboBox<String> threshold = new JComboBox<>();
75
76   private JSlider slider = new JSlider();
77
78   private JTextField thresholdValue = new JTextField(20);
79
80   // TODO implement GUI for tolower flag
81   // JCheckBox toLower = new JCheckBox();
82
83   private JCheckBox thresholdIsMin = new JCheckBox();
84
85   private JCheckBox colourByLabel = new JCheckBox();
86
87   private GraphLine threshline;
88
89   private Color oldmaxColour;
90
91   private Color oldminColour;
92
93   private ActionListener colourEditor = null;
94
95   /**
96    * Constructor
97    * 
98    * @param frender
99    * @param theType
100    */
101   public FeatureColourChooser(FeatureRenderer frender, String theType)
102   {
103     this(frender, false, theType);
104   }
105
106   /**
107    * Constructor, with option to make a blocking dialog (has to complete in the
108    * AWT event queue thread). Currently this option is always set to false.
109    * 
110    * @param frender
111    * @param blocking
112    * @param theType
113    */
114   FeatureColourChooser(FeatureRenderer frender, boolean blocking,
115           String theType)
116   {
117     this.fr = frender;
118     this.type = theType;
119     ap = fr.ap;
120     String title = MessageManager
121             .formatMessage("label.graduated_color_for_params", new String[]
122             { theType });
123     initDialogFrame(this, true, blocking, title, 480, 185);
124
125     slider.addChangeListener(new ChangeListener()
126     {
127       @Override
128       public void stateChanged(ChangeEvent evt)
129       {
130         if (!adjusting)
131         {
132           thresholdValue.setText((slider.getValue() / scaleFactor) + "");
133           sliderValueChanged();
134         }
135       }
136     });
137     slider.addMouseListener(new MouseAdapter()
138     {
139       @Override
140       public void mouseReleased(MouseEvent evt)
141       {
142         /*
143          * only update Overview and/or structure colouring
144          * when threshold slider drag ends (mouse up)
145          */
146         if (ap != null)
147         {
148           ap.paintAlignment(true);
149         }
150       }
151     });
152
153     float mm[] = fr.getMinMax().get(theType)[0];
154     min = mm[0];
155     max = mm[1];
156
157     /*
158      * ensure scale factor allows a scaled range with
159      * 10 integer divisions ('ticks'); if we have got here,
160      * we should expect that max != min
161      */
162     scaleFactor = (max == min) ? 1f : 100f / (max - min);
163
164     oldcs = fr.getFeatureColours().get(theType);
165     if (!oldcs.isSimpleColour())
166     {
167       if (oldcs.isAutoScaled())
168       {
169         // update the scale
170         cs = new FeatureColour((FeatureColour) oldcs, min, max);
171       }
172       else
173       {
174         cs = new FeatureColour((FeatureColour) oldcs);
175       }
176     }
177     else
178     {
179       // promote original color to a graduated color
180       Color bl = oldcs.getColour();
181       if (bl == null)
182       {
183         bl = Color.BLACK;
184       }
185       // original colour becomes the maximum colour
186       cs = new FeatureColour(Color.white, bl, mm[0], mm[1]);
187       cs.setColourByLabel(false);
188     }
189     minColour.setBackground(oldminColour = cs.getMinColour());
190     maxColour.setBackground(oldmaxColour = cs.getMaxColour());
191     adjusting = true;
192
193     try
194     {
195       jbInit();
196     } catch (Exception ex)
197     {
198     }
199     // update the gui from threshold state
200     thresholdIsMin.setSelected(!cs.isAutoScaled());
201     colourByLabel.setSelected(cs.isColourByLabel());
202     if (cs.hasThreshold())
203     {
204       // initialise threshold slider and selector
205       threshold.setSelectedIndex(cs.isAboveThreshold() ? 1 : 2);
206       slider.setEnabled(true);
207       slider.setValue((int) (cs.getThreshold() * scaleFactor));
208       thresholdValue.setEnabled(true);
209       threshline = new GraphLine((max - min) / 2f, "Threshold",
210               Color.black);
211       threshline.value = cs.getThreshold();
212     }
213
214     adjusting = false;
215
216     changeColour(false);
217     waitForInput();
218   }
219
220   private void jbInit() throws Exception
221   {
222
223     minColour.setFont(JvSwingUtils.getLabelFont());
224     minColour.setBorder(BorderFactory.createLineBorder(Color.black));
225     minColour.setPreferredSize(new Dimension(40, 20));
226     minColour.setToolTipText(MessageManager.getString("label.min_colour"));
227     minColour.addMouseListener(new MouseAdapter()
228     {
229       @Override
230       public void mousePressed(MouseEvent e)
231       {
232         if (minColour.isEnabled())
233         {
234           minColour_actionPerformed();
235         }
236       }
237     });
238     maxColour.setFont(JvSwingUtils.getLabelFont());
239     maxColour.setBorder(BorderFactory.createLineBorder(Color.black));
240     maxColour.setPreferredSize(new Dimension(40, 20));
241     maxColour.setToolTipText(MessageManager.getString("label.max_colour"));
242     maxColour.addMouseListener(new MouseAdapter()
243     {
244       @Override
245       public void mousePressed(MouseEvent e)
246       {
247         if (maxColour.isEnabled())
248         {
249           maxColour_actionPerformed();
250         }
251       }
252     });
253     maxColour.setBorder(new LineBorder(Color.black));
254     JLabel minText = new JLabel(MessageManager.getString("label.min"));
255     minText.setFont(JvSwingUtils.getLabelFont());
256     JLabel maxText = new JLabel(MessageManager.getString("label.max"));
257     maxText.setFont(JvSwingUtils.getLabelFont());
258     this.setLayout(new BorderLayout());
259     JPanel jPanel1 = new JPanel();
260     jPanel1.setBackground(Color.white);
261     JPanel jPanel2 = new JPanel();
262     jPanel2.setLayout(new FlowLayout());
263     jPanel2.setBackground(Color.white);
264     threshold.addActionListener(new ActionListener()
265     {
266       @Override
267       public void actionPerformed(ActionEvent e)
268       {
269         threshold_actionPerformed();
270       }
271     });
272     threshold.setToolTipText(MessageManager
273             .getString("label.threshold_feature_display_by_score"));
274     threshold.addItem(MessageManager
275             .getString("label.threshold_feature_no_threshold")); // index 0
276     threshold.addItem(MessageManager
277             .getString("label.threshold_feature_above_threshold")); // index 1
278     threshold.addItem(MessageManager
279             .getString("label.threshold_feature_below_threshold")); // index 2
280
281     JPanel jPanel3 = new JPanel();
282     jPanel3.setLayout(new FlowLayout());
283     thresholdValue.addActionListener(new ActionListener()
284     {
285       @Override
286       public void actionPerformed(ActionEvent e)
287       {
288         thresholdValue_actionPerformed();
289       }
290     });
291     slider.setPaintLabels(false);
292     slider.setPaintTicks(true);
293     slider.setBackground(Color.white);
294     slider.setEnabled(false);
295     slider.setOpaque(false);
296     slider.setPreferredSize(new Dimension(100, 32));
297     slider.setToolTipText(
298             MessageManager.getString("label.adjust_threshold"));
299     thresholdValue.setEnabled(false);
300     thresholdValue.setColumns(7);
301     jPanel3.setBackground(Color.white);
302     thresholdIsMin.setBackground(Color.white);
303     thresholdIsMin
304             .setText(MessageManager.getString("label.threshold_minmax"));
305     thresholdIsMin.setToolTipText(MessageManager
306             .getString("label.toggle_absolute_relative_display_threshold"));
307     thresholdIsMin.addActionListener(new ActionListener()
308     {
309       @Override
310       public void actionPerformed(ActionEvent actionEvent)
311       {
312         thresholdIsMin_actionPerformed();
313       }
314     });
315     colourByLabel.setBackground(Color.white);
316     colourByLabel
317             .setText(MessageManager.getString("label.colour_by_label"));
318     colourByLabel.setToolTipText(MessageManager.getString(
319             "label.display_features_same_type_different_label_using_different_colour"));
320     colourByLabel.addActionListener(new ActionListener()
321     {
322       @Override
323       public void actionPerformed(ActionEvent actionEvent)
324       {
325         colourByLabel_actionPerformed();
326       }
327     });
328
329     JPanel colourPanel = new JPanel();
330     colourPanel.setBackground(Color.white);
331     jPanel1.add(ok);
332     jPanel1.add(cancel);
333     jPanel2.add(colourByLabel, BorderLayout.WEST);
334     jPanel2.add(colourPanel, BorderLayout.EAST);
335     colourPanel.add(minText);
336     colourPanel.add(minColour);
337     colourPanel.add(maxText);
338     colourPanel.add(maxColour);
339     this.add(jPanel3, BorderLayout.CENTER);
340     jPanel3.add(threshold);
341     jPanel3.add(slider);
342     jPanel3.add(thresholdValue);
343     jPanel3.add(thresholdIsMin);
344     this.add(jPanel1, BorderLayout.SOUTH);
345     this.add(jPanel2, BorderLayout.NORTH);
346   }
347
348   /**
349    * Action on clicking the 'minimum colour' - open a colour chooser dialog, and
350    * set the selected colour (if the user does not cancel out of the dialog)
351    */
352   protected void minColour_actionPerformed()
353   {
354     Color col = JColorChooser.showDialog(this,
355             MessageManager.getString("label.select_colour_minimum_value"),
356             minColour.getBackground());
357     if (col != null)
358     {
359       minColour.setBackground(col);
360       minColour.setForeground(col);
361     }
362     minColour.repaint();
363     changeColour(true);
364   }
365
366   /**
367    * Action on clicking the 'maximum colour' - open a colour chooser dialog, and
368    * set the selected colour (if the user does not cancel out of the dialog)
369    */
370   protected void maxColour_actionPerformed()
371   {
372     Color col = JColorChooser.showDialog(this,
373             MessageManager.getString("label.select_colour_maximum_value"),
374             maxColour.getBackground());
375     if (col != null)
376     {
377       maxColour.setBackground(col);
378       maxColour.setForeground(col);
379     }
380     maxColour.repaint();
381     changeColour(true);
382   }
383
384   /**
385    * Constructs and sets the selected colour options as the colour for the
386    * feature type, and repaints the alignment, and optionally the Overview
387    * and/or structure viewer if open
388    * 
389    * @param updateOverview
390    */
391   void changeColour(boolean updateOverview)
392   {
393     // Check if combobox is still adjusting
394     if (adjusting)
395     {
396       return;
397     }
398
399     boolean aboveThreshold = false;
400     boolean belowThreshold = false;
401     if (threshold.getSelectedIndex() == 1)
402     {
403       aboveThreshold = true;
404     }
405     else if (threshold.getSelectedIndex() == 2)
406     {
407       belowThreshold = true;
408     }
409     boolean hasThreshold = aboveThreshold || belowThreshold;
410
411     slider.setEnabled(true);
412     thresholdValue.setEnabled(true);
413
414     FeatureColourI acg;
415     if (cs.isColourByLabel())
416     {
417       acg = new FeatureColour(oldminColour, oldmaxColour, min, max);
418     }
419     else
420     {
421       acg = new FeatureColour(oldminColour = minColour.getBackground(),
422               oldmaxColour = maxColour.getBackground(), min, max);
423     }
424
425     if (!hasThreshold)
426     {
427       slider.setEnabled(false);
428       thresholdValue.setEnabled(false);
429       thresholdValue.setText("");
430       thresholdIsMin.setEnabled(false);
431     }
432     else if (threshline == null)
433     {
434       /*
435        * todo not yet implemented: visual indication of feature threshold
436        */
437       threshline = new GraphLine((max - min) / 2f, "Threshold",
438               Color.black);
439     }
440
441     if (hasThreshold)
442     {
443       adjusting = true;
444       acg.setThreshold(threshline.value);
445
446       float range = (max - min) * scaleFactor;
447
448       slider.setMinimum((int) (min * scaleFactor));
449       slider.setMaximum((int) (max * scaleFactor));
450       // slider.setValue((int) (threshline.value * scaleFactor));
451       slider.setValue(Math.round(threshline.value * scaleFactor));
452       thresholdValue.setText(threshline.value + "");
453       slider.setMajorTickSpacing((int) (range / 10f));
454       slider.setEnabled(true);
455       thresholdValue.setEnabled(true);
456       thresholdIsMin.setEnabled(!colourByLabel.isSelected());
457       adjusting = false;
458     }
459
460     acg.setAboveThreshold(aboveThreshold);
461     acg.setBelowThreshold(belowThreshold);
462     if (thresholdIsMin.isSelected() && hasThreshold)
463     {
464       acg.setAutoScaled(false);
465       if (aboveThreshold)
466       {
467         acg = new FeatureColour((FeatureColour) acg, threshline.value, max);
468       }
469       else
470       {
471         acg = new FeatureColour((FeatureColour) acg, min, threshline.value);
472       }
473     }
474     else
475     {
476       acg.setAutoScaled(true);
477     }
478     acg.setColourByLabel(colourByLabel.isSelected());
479     if (acg.isColourByLabel())
480     {
481       maxColour.setEnabled(false);
482       minColour.setEnabled(false);
483       maxColour.setBackground(this.getBackground());
484       maxColour.setForeground(this.getBackground());
485       minColour.setBackground(this.getBackground());
486       minColour.setForeground(this.getBackground());
487
488     }
489     else
490     {
491       maxColour.setEnabled(true);
492       minColour.setEnabled(true);
493       maxColour.setBackground(oldmaxColour);
494       minColour.setBackground(oldminColour);
495       maxColour.setForeground(oldmaxColour);
496       minColour.setForeground(oldminColour);
497     }
498     fr.setColour(type, acg);
499     cs = acg;
500     ap.paintAlignment(updateOverview);
501   }
502
503   @Override
504   protected void raiseClosed()
505   {
506     if (this.colourEditor != null)
507     {
508       colourEditor.actionPerformed(new ActionEvent(this, 0, "CLOSED"));
509     }
510   }
511
512   @Override
513   public void okPressed()
514   {
515     changeColour(false);
516   }
517
518   @Override
519   public void cancelPressed()
520   {
521     reset();
522   }
523
524   /**
525    * Action when the user cancels the dialog. All previous settings should be
526    * restored and rendered on the alignment, and any linked Overview window or
527    * structure.
528    */
529   void reset()
530   {
531     fr.setColour(type, oldcs);
532     ap.paintAlignment(true);
533     cs = null;
534   }
535
536   /**
537    * Action on change of choice of No / Above / Below Threshold
538    */
539   protected void threshold_actionPerformed()
540   {
541     changeColour(true);
542   }
543
544   /**
545    * Action on text entry of a threshold value
546    */
547   protected void thresholdValue_actionPerformed()
548   {
549     try
550     {
551       float f = Float.parseFloat(thresholdValue.getText());
552       slider.setValue((int) (f * scaleFactor));
553       threshline.value = f;
554
555       /*
556        * force repaint of any Overview window or structure
557        */
558       ap.paintAlignment(true);
559     } catch (NumberFormatException ex)
560     {
561     }
562   }
563
564   /**
565    * Action on change of threshold slider value. This may be done interactively
566    * (by moving the slider), or programmatically (to update the slider after
567    * manual input of a threshold value).
568    */
569   protected void sliderValueChanged()
570   {
571     /*
572      * squash rounding errors by forcing min/max of slider to 
573      * actual min/max of feature score range
574      */
575     int value = slider.getValue();
576     threshline.value = value == slider.getMaximum() ? max
577             : (value == slider.getMinimum() ? min : value / scaleFactor);
578     cs.setThreshold(threshline.value);
579
580     /*
581      * repaint alignment, but not Overview or structure,
582      * to avoid overload while dragging the slider
583      */
584     changeColour(false);
585   }
586
587   protected void thresholdIsMin_actionPerformed()
588   {
589     changeColour(true);
590   }
591
592   protected void colourByLabel_actionPerformed()
593   {
594     changeColour(true);
595   }
596
597   void addActionListener(ActionListener graduatedColorEditor)
598   {
599     if (colourEditor != null)
600     {
601       System.err.println(
602               "IMPLEMENTATION ISSUE: overwriting action listener for FeatureColourChooser");
603     }
604     colourEditor = graduatedColorEditor;
605   }
606
607   /**
608    * Answers the last colour setting selected by user - either oldcs (which may
609    * be a java.awt.Color) or the new GraduatedColor
610    * 
611    * @return
612    */
613   FeatureColourI getLastColour()
614   {
615     if (cs == null)
616     {
617       return oldcs;
618     }
619     return cs;
620   }
621
622 }