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