Merge branch 'feature/JAL-2664' into feature/JAL-2527
[jalview.git] / src / jalview / appletgui / 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.appletgui;
22
23 import jalview.api.FeatureColourI;
24 import jalview.datamodel.GraphLine;
25 import jalview.schemes.AnnotationColourGradient;
26 import jalview.schemes.FeatureColour;
27 import jalview.util.MessageManager;
28
29 import java.awt.Checkbox;
30 import java.awt.Choice;
31 import java.awt.Color;
32 import java.awt.Dimension;
33 import java.awt.FlowLayout;
34 import java.awt.Font;
35 import java.awt.Frame;
36 import java.awt.GridLayout;
37 import java.awt.Label;
38 import java.awt.Panel;
39 import java.awt.Scrollbar;
40 import java.awt.TextField;
41 import java.awt.event.ActionEvent;
42 import java.awt.event.ActionListener;
43 import java.awt.event.AdjustmentEvent;
44 import java.awt.event.AdjustmentListener;
45 import java.awt.event.ItemEvent;
46 import java.awt.event.ItemListener;
47 import java.awt.event.MouseEvent;
48 import java.awt.event.MouseListener;
49
50 public class FeatureColourChooser extends Panel implements ActionListener,
51         AdjustmentListener, ItemListener, MouseListener
52 {
53   /*
54    * the absolute min-max range of a feature score is scaled to 
55    * 1000 positions on the colour threshold slider
56    */
57   private static final int SCALE_FACTOR_1K = 1000;
58
59   private JVDialog frame;
60
61   private Frame owner;
62
63   private FeatureRenderer fr;
64
65   private FeatureSettings fs = null;
66
67   private FeatureColourI cs;
68
69   private FeatureColourI oldcs;
70
71   private boolean adjusting = false;
72
73   private float min, max;
74
75   private String type = null;
76
77   private AlignFrame af = null;
78
79   private Panel minColour = new Panel();
80
81   private Panel maxColour = new Panel();
82
83   private Choice threshold = new Choice();
84
85   private Scrollbar slider = new Scrollbar(Scrollbar.HORIZONTAL);
86
87   private TextField thresholdValue = new TextField(20);
88
89   private Checkbox thresholdIsMin = new Checkbox();
90
91   private Checkbox colourFromLabel = new Checkbox();
92
93   private GraphLine threshline;
94
95   /**
96    * Constructor given a context AlignFrame and a feature type. This is used
97    * when opening the graduated colour dialog from the Amend Feature dialog.
98    * 
99    * @param alignFrame
100    * @param featureType
101    */
102   public FeatureColourChooser(AlignFrame alignFrame, String featureType)
103   {
104     this.af = alignFrame;
105     init(alignFrame.getSeqcanvas().getFeatureRenderer(), featureType);
106   }
107
108   /**
109    * Constructor given a context FeatureSettings and a feature type. This is
110    * used when opening the graduated colour dialog from Feature Settings.
111    * 
112    * @param fsettings
113    * @param featureType
114    */
115   public FeatureColourChooser(FeatureSettings fsettings, String featureType)
116   {
117     this.fs = fsettings;
118     init(fsettings.fr, featureType);
119   }
120
121   private void init(FeatureRenderer frenderer, String featureType)
122   {
123     this.type = featureType;
124     fr = frenderer;
125     float mm[] = fr.getMinMax().get(type)[0];
126     min = mm[0];
127     max = mm[1];
128     threshline = new GraphLine((max - min) / 2f, "Threshold", Color.black);
129     oldcs = fr.getFeatureColours().get(type);
130     if (oldcs.isGraduatedColour())
131     {
132       threshline.value = oldcs.getThreshold();
133       cs = new FeatureColour((FeatureColour) oldcs, min, max);
134     }
135     else
136     {
137       // promote original color to a graduated color
138       Color bl = Color.black;
139       if (oldcs.isSimpleColour())
140       {
141         bl = oldcs.getColour();
142       }
143       // original colour becomes the maximum colour
144       cs = new FeatureColour(Color.white, bl, mm[0], mm[1]);
145     }
146     minColour.setBackground(cs.getMinColour());
147     maxColour.setBackground(cs.getMaxColour());
148     minColour.setForeground(cs.getMinColour());
149     maxColour.setForeground(cs.getMaxColour());
150     colourFromLabel.setState(cs.isColourByLabel());
151     adjusting = true;
152
153     try
154     {
155       jbInit();
156     } catch (Exception ex)
157     {
158     }
159     threshold.select(
160             cs.isAboveThreshold() ? 1 : (cs.isBelowThreshold() ? 2 : 0));
161
162     adjusting = false;
163     changeColour(true);
164     colourFromLabel.addItemListener(this);
165     slider.addAdjustmentListener(this);
166     slider.addMouseListener(this);
167     owner = (af != null) ? af : fs.frame;
168     frame = new JVDialog(owner, MessageManager
169             .formatMessage("label.graduated_color_for_params", new String[]
170             { type }), true, 480, 248);
171     frame.setMainPanel(this);
172     validate();
173     frame.setVisible(true);
174     if (frame.accept)
175     {
176       changeColour(true);
177     }
178     else
179     {
180       // cancel
181       reset();
182       frame.setVisible(false);
183     }
184   }
185
186   public FeatureColourChooser()
187   {
188     try
189     {
190       jbInit();
191     } catch (Exception ex)
192     {
193       ex.printStackTrace();
194     }
195   }
196
197   private void jbInit() throws Exception
198   {
199     Label minLabel = new Label(MessageManager.getString("label.min")),
200             maxLabel = new Label(MessageManager.getString("label.max"));
201     minLabel.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
202     maxLabel.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
203     // minColour.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
204     // minColour.setLabel("Min Colour");
205
206     minColour.setBounds(0, 0, 40, 27);
207     maxColour.setBounds(0, 0, 40, 27);
208     minColour.addMouseListener(this);
209
210     maxColour.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
211     maxColour.addMouseListener(this);
212
213     thresholdIsMin.addItemListener(this);
214
215     this.setLayout(new GridLayout(4, 1));
216     Panel jPanel1 = new Panel();
217     jPanel1.setLayout(new FlowLayout());
218     Panel jPanel2 = new Panel();
219     jPanel2.setLayout(new FlowLayout());
220     Panel jPanel3 = new Panel();
221     jPanel3.setLayout(new GridLayout(1, 1));
222     Panel jPanel4 = new Panel();
223     jPanel4.setLayout(new FlowLayout());
224     jPanel1.setBackground(Color.white);
225     jPanel2.setBackground(Color.white);
226     jPanel4.setBackground(Color.white);
227     threshold.addItemListener(this);
228     threshold.addItem(MessageManager
229             .getString("label.threshold_feature_no_threshold"));
230     threshold.addItem(MessageManager
231             .getString("label.threshold_feature_above_threshold"));
232     threshold.addItem(MessageManager
233             .getString("label.threshold_feature_below_threshold"));
234     thresholdValue.addActionListener(this);
235     slider.setBackground(Color.white);
236     slider.setEnabled(false);
237     slider.setSize(new Dimension(93, 21));
238     thresholdValue.setEnabled(false);
239     thresholdValue.setSize(new Dimension(79, 22)); // setBounds(new
240                                                    // Rectangle(248, 2, 79,
241                                                    // 22));
242     thresholdValue.setColumns(5);
243     jPanel3.setBackground(Color.white);
244
245     colourFromLabel.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
246     colourFromLabel
247             .setLabel(MessageManager.getString("label.colour_by_label"));
248     colourFromLabel.setSize(new Dimension(139, 22));
249     // threshold.setBounds(new Rectangle(11, 3, 139, 22));
250     thresholdIsMin.setBackground(Color.white);
251     thresholdIsMin
252             .setLabel(MessageManager.getString("label.threshold_minmax"));
253     thresholdIsMin.setSize(new Dimension(135, 23));
254     // thresholdIsMin.setBounds(new Rectangle(328, 3, 135, 23));
255     jPanel1.add(minLabel);
256     jPanel1.add(minColour);
257     jPanel1.add(maxLabel);
258     jPanel1.add(maxColour);
259     jPanel1.add(colourFromLabel);
260     jPanel2.add(threshold);
261     jPanel3.add(slider);
262     jPanel4.add(thresholdValue);
263     jPanel4.add(thresholdIsMin);
264     this.add(jPanel1);// , java.awt.BorderLayout.NORTH);
265     this.add(jPanel2);// , java.awt.BorderLayout.NORTH);
266     this.add(jPanel3);// , java.awt.BorderLayout.CENTER);
267     this.add(jPanel4);// , java.awt.BorderLayout.CENTER);
268   }
269
270   @Override
271   public void actionPerformed(ActionEvent evt)
272   {
273     if (evt.getSource() == thresholdValue)
274     {
275       try
276       {
277         float f = new Float(thresholdValue.getText()).floatValue();
278         slider.setValue((int) (f * SCALE_FACTOR_1K));
279         adjustmentValueChanged(null);
280
281         /*
282          * force repaint of any Overview window or structure
283          */
284         changeColour(true);
285       } catch (NumberFormatException ex)
286       {
287       }
288     }
289     else if (evt.getSource() == minColour)
290     {
291       minColour_actionPerformed(null);
292     }
293     else if (evt.getSource() == maxColour)
294     {
295       maxColour_actionPerformed(null);
296     }
297     else
298     {
299       changeColour(true);
300     }
301   }
302
303   @Override
304   public void itemStateChanged(ItemEvent evt)
305   {
306     maxColour.setEnabled(!colourFromLabel.getState());
307     minColour.setEnabled(!colourFromLabel.getState());
308     changeColour(true);
309   }
310
311   /**
312    * Handler called when the value of the threshold slider changes, either by
313    * user action or programmatically
314    */
315   @Override
316   public void adjustmentValueChanged(AdjustmentEvent evt)
317   {
318     if (!adjusting)
319     {
320       thresholdValue.setText((slider.getValue() / 1000f) + "");
321       valueChanged();
322     }
323   }
324
325   /**
326    * Responds to a change of colour threshold by computing the absolute value
327    * and refreshing the alignment.
328    */
329   protected void valueChanged()
330   {
331     threshline.value = slider.getValue() / 1000f;
332     cs.setThreshold(threshline.value);
333     changeColour(false);
334     PaintRefresher.Refresh(this, fr.getViewport().getSequenceSetId());
335   }
336
337   public void minColour_actionPerformed(Color newCol)
338   {
339     if (newCol == null)
340     {
341       new UserDefinedColours(this, minColour.getBackground(), owner,
342               MessageManager
343                       .getString("label.select_colour_minimum_value"));
344     }
345     else
346     {
347       minColour.setBackground(newCol);
348       minColour.setForeground(newCol);
349       minColour.repaint();
350       changeColour(true);
351     }
352
353   }
354
355   public void maxColour_actionPerformed(Color newCol)
356   {
357     if (newCol == null)
358     {
359       new UserDefinedColours(this, maxColour.getBackground(), owner,
360               MessageManager
361                       .getString("label.select_colour_maximum_value"));
362     }
363     else
364     {
365       maxColour.setBackground(newCol);
366       maxColour.setForeground(newCol);
367       maxColour.repaint();
368       changeColour(true);
369     }
370   }
371
372   void changeColour(boolean updateOverview)
373   {
374     // Check if combobox is still adjusting
375     if (adjusting)
376     {
377       return;
378     }
379
380     int thresholdOption = AnnotationColourGradient.NO_THRESHOLD;
381     if (threshold.getSelectedIndex() == 1)
382     {
383       thresholdOption = AnnotationColourGradient.ABOVE_THRESHOLD;
384     }
385     else if (threshold.getSelectedIndex() == 2)
386     {
387       thresholdOption = AnnotationColourGradient.BELOW_THRESHOLD;
388     }
389
390     slider.setEnabled(true);
391     thresholdValue.setEnabled(true);
392     FeatureColour acg = new FeatureColour(minColour.getBackground(),
393             maxColour.getBackground(), min, max);
394
395     acg.setColourByLabel(colourFromLabel.getState());
396     maxColour.setEnabled(!colourFromLabel.getState());
397     minColour.setEnabled(!colourFromLabel.getState());
398     if (thresholdOption == AnnotationColourGradient.NO_THRESHOLD)
399     {
400       slider.setEnabled(false);
401       thresholdValue.setEnabled(false);
402       thresholdValue.setText("");
403     }
404
405     if (thresholdOption != AnnotationColourGradient.NO_THRESHOLD)
406     {
407       adjusting = true;
408       acg.setThreshold(threshline.value);
409
410       slider.setMinimum((int) (min * SCALE_FACTOR_1K));
411       slider.setMaximum((int) (max * SCALE_FACTOR_1K));
412       slider.setValue((int) (threshline.value * SCALE_FACTOR_1K));
413       thresholdValue.setText(threshline.value + "");
414       slider.setEnabled(true);
415       thresholdValue.setEnabled(true);
416       adjusting = false;
417     }
418
419     acg.setAboveThreshold(
420             thresholdOption == AnnotationColourGradient.ABOVE_THRESHOLD);
421     acg.setBelowThreshold(
422             thresholdOption == AnnotationColourGradient.BELOW_THRESHOLD);
423
424     if (thresholdIsMin.getState()
425             && thresholdOption != AnnotationColourGradient.NO_THRESHOLD)
426     {
427       if (thresholdOption == AnnotationColourGradient.ABOVE_THRESHOLD)
428       {
429         acg = new FeatureColour(acg, threshline.value, max);
430       }
431       else
432       {
433         acg = new FeatureColour(acg, min, threshline.value);
434       }
435     }
436
437     fr.setColour(type, acg);
438     cs = acg;
439     fs.selectionChanged(updateOverview);
440   }
441
442   void reset()
443   {
444     fr.setColour(type, oldcs);
445     fs.selectionChanged(true);
446   }
447
448   @Override
449   public void mouseClicked(MouseEvent evt)
450   {
451   }
452
453   @Override
454   public void mousePressed(MouseEvent evt)
455   {
456   }
457
458   @Override
459   public void mouseReleased(MouseEvent evt)
460   {
461     if (evt.getSource() == minColour)
462     {
463       minColour_actionPerformed(null);
464     }
465     else if (evt.getSource() == maxColour)
466     {
467       maxColour_actionPerformed(null);
468     }
469     else
470     {
471       changeColour(true);
472       // PaintRefresher.Refresh(this, fr.getViewport().getSequenceSetId());
473     }
474   }
475
476   @Override
477   public void mouseEntered(MouseEvent evt)
478   {
479   }
480
481   @Override
482   public void mouseExited(MouseEvent evt)
483   {
484   }
485
486 }