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