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