graduated feature schemes (with visual indication of thresholding)
[jalview.git] / src / jalview / gui / FeatureColourChooser.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Development Version 2.4.1)
3  * Copyright (C) 2009 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle
4  * 
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  * 
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  * 
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
18  */
19 package jalview.gui;
20
21 import java.util.*;
22
23 import java.awt.*;
24 import java.awt.event.*;
25 import javax.swing.*;
26 import javax.swing.border.LineBorder;
27 import javax.swing.event.*;
28
29 import jalview.datamodel.*;
30 import jalview.schemes.*;
31 import java.awt.Dimension;
32
33 public class FeatureColourChooser extends JPanel 
34 {
35   JDialog frame;
36   
37
38 //  FeatureSettings fs;
39   FeatureRenderer fr;
40   
41   
42   private GraduatedColor cs;
43   private Object oldcs;
44   /**
45    * 
46    * @return the last colour setting selected by user - either oldcs (which may be a java.awt.Color) or the new GraduatedColor
47    */
48   public Object getLastColour() {
49     if (cs==null)
50     {
51       return oldcs;
52     }
53     return cs;
54   }
55   Hashtable oldgroupColours;
56   
57   AlignmentPanel ap;
58   
59
60   boolean adjusting = false;
61
62   private float min;
63
64   private float max;
65   String type = null;
66   public FeatureColourChooser(FeatureRenderer frender, String type)
67   {
68     this(frender,false,type);
69   }
70     public FeatureColourChooser(FeatureRenderer frender, boolean block, String type)
71     {
72     this.fr = frender;
73     this.type = type;
74     ap = fr.ap;
75     frame = new JDialog(Desktop.instance,true);
76     frame.setTitle("Graduated Feature Colour for "+type);
77     Rectangle deskr = Desktop.instance.getBounds();
78     frame.setBounds(new Rectangle((int) (deskr.getCenterX()-240),(int) (deskr.getCenterY()-92),480,185));
79     frame.setContentPane(this);
80     //frame.setLayer(JLayeredPane.PALETTE_LAYER);
81     //Desktop.addInternalFrame(frame, "Graduated Feature Colour for "+type, 480, 145);
82
83     slider.addChangeListener(new ChangeListener()
84     {
85       public void stateChanged(ChangeEvent evt)
86       {
87         if (!adjusting)
88         {
89           thresholdValue.setText(((float) slider.getValue() / 1000f) + "");
90           valueChanged();
91         }
92       }
93     });
94     slider.addMouseListener(new MouseAdapter()
95     {
96       public void mouseReleased(MouseEvent evt)
97       {
98         if (ap!=null) { ap.paintAlignment(true); };
99       }
100     });
101
102     float mm[] = ((float[][]) fr.minmax.get(type))[0];
103     min = mm[0];
104     max = mm[1];
105     oldcs = fr.featureColours.get(type);
106     if (oldcs instanceof GraduatedColor)
107     {
108       if (((GraduatedColor)oldcs).isAutoScale())
109       {
110         // update the scale
111         cs = new GraduatedColor((GraduatedColor) oldcs, min, max);
112       } else {
113         cs = new GraduatedColor((GraduatedColor) oldcs);
114       }
115     } else {
116       // promote original color to a graduated color
117       Color bl = Color.black;
118       if (oldcs instanceof Color)
119       {
120         bl = (Color) oldcs;
121       }
122       // original colour becomes the maximum colour
123       cs = new GraduatedColor(Color.white,bl,mm[0],mm[1]);
124       cs.setColourByLabel(false);
125     }
126     minColour.setBackground(oldminColour=cs.getMinColor());
127     maxColour.setBackground(oldmaxColour=cs.getMaxColor());
128     adjusting = true;
129     
130     try
131     {
132       jbInit();
133     } catch (Exception ex)
134     {
135     }
136     // update the gui from threshold state
137     thresholdIsMin.setSelected(!cs.isAutoScale());
138     colourByLabel.setSelected(cs.isColourByLabel());
139     if (cs.getThreshType()!=AnnotationColourGradient.NO_THRESHOLD)
140     {
141       // initialise threshold slider and selector
142       threshold.setSelectedIndex(cs.getThreshType()==AnnotationColourGradient.ABOVE_THRESHOLD ? 1 : 2);
143             slider.setEnabled(true);
144       thresholdValue.setEnabled(true);
145       threshline = new jalview.datamodel.GraphLine(
146                         (max - min) / 2f,
147                         "Threshold", Color.black);
148       
149     } 
150
151     adjusting = false;
152
153     changeColour();
154     if (!block)
155     {
156       new Thread(new Runnable() {
157
158       public void run()
159       {
160         frame.show();
161       }
162       
163     }).start();
164     } else {
165       frame.show();
166     }
167   }
168
169   public FeatureColourChooser()
170   {
171     try
172     {
173       jbInit();
174     } catch (Exception ex)
175     {
176       ex.printStackTrace();
177     }
178   }
179
180   private void jbInit() throws Exception
181   {
182     
183     minColour.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
184     minColour.setBorder(BorderFactory.createLineBorder(Color.black));
185     minColour.setPreferredSize(new Dimension(40, 20));
186     minColour.setToolTipText("Minimum Colour");
187     minColour.addMouseListener(new MouseAdapter()
188     {
189       public void mousePressed(MouseEvent e)
190       {
191         if (minColour.isEnabled())
192         {
193           minColour_actionPerformed();
194         }
195       }
196     });
197     maxColour.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
198     maxColour.setBorder(BorderFactory.createLineBorder(Color.black));
199     maxColour.setPreferredSize(new Dimension(40, 20));
200     maxColour.setToolTipText("Maximum Colour");
201     maxColour.addMouseListener(new MouseAdapter()
202     {
203       public void mousePressed(MouseEvent e)
204       {
205         if (maxColour.isEnabled())
206         {
207           maxColour_actionPerformed();
208         }
209       }
210     });
211     maxColour.setBorder(new LineBorder(Color.black));
212     minText.setText("Min:");
213     minText.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
214     maxText.setText("Max:");
215     maxText.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
216     ok.setOpaque(false);
217     ok.setText("OK");
218     ok.addActionListener(new ActionListener()
219     {
220       public void actionPerformed(ActionEvent e)
221       {
222         ok_actionPerformed(e);
223       }
224     });
225     cancel.setOpaque(false);
226     cancel.setText("Cancel");
227     cancel.addActionListener(new ActionListener()
228     {
229       public void actionPerformed(ActionEvent e)
230       {
231         cancel_actionPerformed(e);
232       }
233     });
234     this.setLayout(borderLayout1);
235     jPanel2.setLayout(flowLayout1);
236     jPanel1.setBackground(Color.white);
237     jPanel2.setBackground(Color.white);
238     threshold.addActionListener(new ActionListener()
239     {
240       public void actionPerformed(ActionEvent e)
241       {
242         threshold_actionPerformed(e);
243       }
244     });
245     threshold.setToolTipText("Threshold the feature display by score.");
246     threshold.addItem("No Threshold"); // index 0
247     threshold.addItem("Above Threshold"); // index 1
248     threshold.addItem("Below Threshold"); // index 2
249     jPanel3.setLayout(flowLayout2);
250     thresholdValue.addActionListener(new ActionListener()
251     {
252       public void actionPerformed(ActionEvent e)
253       {
254         thresholdValue_actionPerformed(e);
255       }
256     });
257     slider.setPaintLabels(false);
258     slider.setPaintTicks(true);
259     slider.setBackground(Color.white);
260     slider.setEnabled(false);
261     slider.setOpaque(false);
262     slider.setPreferredSize(new Dimension(100, 32));
263     slider.setToolTipText("Adjust threshold");
264     thresholdValue.setEnabled(false);
265     thresholdValue.setColumns(7);
266     jPanel3.setBackground(Color.white);
267     thresholdIsMin.setBackground(Color.white);
268     thresholdIsMin.setText("Threshold is Min/Max");
269     thresholdIsMin.setToolTipText("Toggle between absolute and relative display threshold.");
270     thresholdIsMin.addActionListener(new ActionListener()
271     {
272       public void actionPerformed(ActionEvent actionEvent)
273       {
274         thresholdIsMin_actionPerformed(actionEvent);
275       }
276     });
277     colourByLabel.setBackground(Color.white);
278     colourByLabel.setText("Colour by Label");
279     colourByLabel.setToolTipText("Display features of the same type with a different label using a different colour. (e.g. domain features)");
280     colourByLabel.addActionListener(new ActionListener()
281     {
282       public void actionPerformed(ActionEvent actionEvent)
283       {
284         colourByLabel_actionPerformed(actionEvent);
285       }
286     });
287     colourPanel.setBackground(Color.white);
288     jPanel1.add(ok);
289     jPanel1.add(cancel);
290     jPanel2.add(colourByLabel,java.awt.BorderLayout.WEST);
291     jPanel2.add(colourPanel,java.awt.BorderLayout.EAST);
292     colourPanel.add(minText);
293     colourPanel.add(minColour);
294     colourPanel.add(maxText);
295     colourPanel.add(maxColour);
296     this.add(jPanel3, java.awt.BorderLayout.CENTER);
297     jPanel3.add(threshold);
298     jPanel3.add(slider);
299     jPanel3.add(thresholdValue);
300     jPanel3.add(thresholdIsMin);
301     this.add(jPanel1, java.awt.BorderLayout.SOUTH);
302     this.add(jPanel2, java.awt.BorderLayout.NORTH);
303   }
304
305
306   JLabel minText = new JLabel();
307   JLabel maxText = new JLabel();
308   JPanel minColour = new JPanel();
309
310   JPanel maxColour = new JPanel();
311
312   JButton ok = new JButton();
313
314   JButton cancel = new JButton();
315   JPanel colourPanel = new JPanel();
316   JPanel jPanel1 = new JPanel();
317
318   JPanel jPanel2 = new JPanel();
319
320   BorderLayout borderLayout1 = new BorderLayout();
321
322   JComboBox threshold = new JComboBox();
323
324   FlowLayout flowLayout1 = new FlowLayout();
325
326   JPanel jPanel3 = new JPanel();
327
328   FlowLayout flowLayout2 = new FlowLayout();
329
330   JSlider slider = new JSlider();
331
332   JTextField thresholdValue = new JTextField(20);
333   // TODO implement GUI for tolower flag
334   // JCheckBox toLower = new JCheckBox();
335
336   JCheckBox thresholdIsMin = new JCheckBox();
337   JCheckBox colourByLabel = new JCheckBox();
338
339   private GraphLine threshline;
340
341
342   private Color oldmaxColour;
343
344
345   private Color oldminColour;
346
347   public void minColour_actionPerformed()
348   {
349     Color col = JColorChooser.showDialog(this,
350             "Select Colour for Minimum Value", minColour.getBackground());
351     if (col != null)
352     {
353       minColour.setBackground(col);
354     }
355     minColour.repaint();
356     changeColour();
357   }
358
359   public void maxColour_actionPerformed()
360   {
361     Color col = JColorChooser.showDialog(this,
362             "Select Colour for Maximum Value", maxColour.getBackground());
363     if (col != null)
364     {
365       maxColour.setBackground(col);
366     }
367     maxColour.repaint();
368     changeColour();
369   }
370
371   void changeColour()
372   {
373     // Check if combobox is still adjusting
374     if (adjusting)
375     {
376       return;
377     }
378
379
380     int aboveThreshold = AnnotationColourGradient.NO_THRESHOLD;
381     if (threshold.getSelectedItem().equals("Above Threshold"))
382     {
383       aboveThreshold = AnnotationColourGradient.ABOVE_THRESHOLD;
384     }
385     else if (threshold.getSelectedItem().equals("Below Threshold"))
386     {
387       aboveThreshold = AnnotationColourGradient.BELOW_THRESHOLD;
388     } 
389
390     slider.setEnabled(true);
391     thresholdValue.setEnabled(true);
392     
393     GraduatedColor acg;
394     if (cs.isColourByLabel())
395       {
396         acg = new GraduatedColor(oldminColour, oldmaxColour, min, max);
397       } else {
398         acg = new GraduatedColor(oldminColour=minColour.getBackground(), oldmaxColour=maxColour.getBackground(), min, max);
399         
400       }
401
402     if (aboveThreshold == AnnotationColourGradient.NO_THRESHOLD)
403     {
404       slider.setEnabled(false);
405       thresholdValue.setEnabled(false);
406       thresholdValue.setText("");
407       thresholdIsMin.setEnabled(false);
408     }
409     else if (aboveThreshold != AnnotationColourGradient.NO_THRESHOLD
410             && threshline == null)
411     {
412       // todo visual indication of feature threshold
413       threshline = new jalview.datamodel.GraphLine(
414                       (max - min) / 2f,
415                       "Threshold", Color.black);
416     }
417
418     if (aboveThreshold != AnnotationColourGradient.NO_THRESHOLD)
419     {
420       adjusting = true;
421       acg.setThresh(threshline.value);
422
423       float range = max * 1000f
424               - min * 1000f;
425
426       slider.setMinimum((int) (min * 1000));
427       slider.setMaximum((int) (max * 1000));
428       slider.setValue((int) (threshline.value * 1000));
429       thresholdValue.setText(threshline.value + "");
430       slider.setMajorTickSpacing((int) (range / 10f));
431       slider.setEnabled(true);
432       thresholdValue.setEnabled(true);
433       thresholdIsMin.setEnabled(!colourByLabel.isSelected());
434       adjusting = false;
435     }
436
437     acg.setThreshType(aboveThreshold);
438     if (thresholdIsMin.isSelected() && aboveThreshold != AnnotationColourGradient.NO_THRESHOLD)
439     {
440       acg.setAutoScaled(false);
441       if (aboveThreshold==AnnotationColourGradient.ABOVE_THRESHOLD)
442       { 
443         acg = new GraduatedColor(acg, threshline.value, max);
444       } else { 
445         acg = new GraduatedColor(acg, min,threshline.value);
446       }
447     } else {
448       acg.setAutoScaled(true);
449     }
450     acg.setColourByLabel(colourByLabel.isSelected());
451     if (acg.isColourByLabel())
452     {
453       maxColour.setEnabled(false);
454       minColour.setEnabled(false);
455       maxColour.setBackground(this.getBackground());
456       minColour.setBackground(this.getBackground());
457     } else {
458       maxColour.setEnabled(true);
459       minColour.setEnabled(true);
460       maxColour.setBackground(oldmaxColour);
461       minColour.setBackground(oldminColour);
462     }
463     fr.featureColours.put(type,acg);
464     cs = acg;
465     ap.paintAlignment(false);
466   }
467   private void raiseClosed() {
468     if (this.colourEditor!=null)
469     {
470       colourEditor.actionPerformed(new ActionEvent(this, 0, "CLOSED"));
471     }
472   }
473   public void ok_actionPerformed(ActionEvent e)
474   {
475     changeColour();
476     try
477     {
478       frame.dispose();
479       raiseClosed();
480     } catch (Exception ex)
481     {
482     }
483   }
484
485   public void cancel_actionPerformed(ActionEvent e)
486   {
487     reset();
488     try
489     {
490       frame.dispose();
491 //      frame.setClosed(true);
492       raiseClosed();
493     } catch (Exception ex)
494     {
495     }
496   }
497
498   void reset()
499   {
500     fr.featureColours.put(type, oldcs);
501     ap.paintAlignment(false);
502     cs = null;
503   }
504
505   public void thresholdCheck_actionPerformed(ActionEvent e)
506   {
507     changeColour();
508   }
509
510   public void annotations_actionPerformed(ActionEvent e)
511   {
512     changeColour();
513   }
514
515   public void threshold_actionPerformed(ActionEvent e)
516   {
517     changeColour();
518   }
519
520   public void thresholdValue_actionPerformed(ActionEvent e)
521   {
522     try
523     {
524       float f = Float.parseFloat(thresholdValue.getText());
525       slider.setValue((int) (f * 1000));
526       threshline.value = f;
527     } catch (NumberFormatException ex)
528     {
529     }
530   }
531
532   public void valueChanged()
533   {
534     threshline.value = (float) slider.getValue() / 1000f;
535     cs.setThresh(threshline.value);
536     changeColour();
537     ap.paintAlignment(false);
538   }
539
540   public void thresholdIsMin_actionPerformed(ActionEvent actionEvent)
541   {
542     changeColour();
543   }
544   public void colourByLabel_actionPerformed(ActionEvent actionEvent)
545   {
546     changeColour();
547   }
548   ActionListener colourEditor=null;
549   public void addActionListener(ActionListener graduatedColorEditor)
550   {
551     if (colourEditor!=null)
552     {
553       System.err.println("IMPLEMENTATION ISSUE: overwriting action listener for FeatureColourChooser");
554     }
555     colourEditor = graduatedColorEditor;
556   }
557
558 }