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