f2036de5f178ed58ee773f7bc4f745f57713fc6b
[jalview.git] / src / jalview / gui / AnnotationColourChooser.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8)
3  * Copyright (C) 2012 J Procter, AM Waterhouse, LM Lui, J Engelhardt, G Barton, M Clamp, S Searle
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 of the License, or (at your option) any later version.
10  *  
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 package jalview.gui;
19
20 import java.util.*;
21
22 import java.awt.*;
23 import java.awt.event.*;
24
25 import javax.swing.*;
26 import javax.swing.event.*;
27
28 import net.miginfocom.swing.MigLayout;
29
30 import jalview.bin.Cache;
31 import jalview.datamodel.*;
32 import jalview.schemes.*;
33 import java.awt.Dimension;
34
35 public class AnnotationColourChooser extends JPanel
36 {
37   JInternalFrame frame;
38
39   AlignViewport av;
40
41   AlignmentPanel ap;
42
43   ColourSchemeI oldcs;
44
45   Hashtable oldgroupColours;
46
47   jalview.datamodel.AlignmentAnnotation currentAnnotation;
48
49   boolean adjusting = false;
50   /**
51    * enabled if the user is dragging the slider - try to keep updates to a minimun
52    */
53   boolean sliderDragging = false;
54
55   public AnnotationColourChooser(AlignViewport av, final AlignmentPanel ap)
56   {
57     oldcs = av.getGlobalColourScheme();
58     if (av.getAlignment().getGroups() != null)
59     {
60       oldgroupColours = new Hashtable();
61       for (SequenceGroup sg : ap.av.getAlignment().getGroups())
62       {
63         if (sg.cs != null)
64         {
65           oldgroupColours.put(sg, sg.cs);
66         }
67       }
68     }
69     this.av = av;
70     this.ap = ap;
71     frame = new JInternalFrame();
72     frame.setContentPane(this);
73     frame.setLayer(JLayeredPane.PALETTE_LAYER);
74     Desktop.addInternalFrame(frame, "Colour by Annotation", 520, 215);
75
76     slider.addChangeListener(new ChangeListener()
77     {
78       public void stateChanged(ChangeEvent evt)
79       {
80         if (!adjusting)
81         {
82           thresholdValue.setText(((float) slider.getValue() / 1000f) + "");
83           valueChanged(!sliderDragging);
84         }
85       }
86     });
87     slider.addMouseListener(new MouseAdapter()
88     {
89       @Override
90       public void mousePressed(MouseEvent e)
91       {
92         sliderDragging=true;
93         super.mousePressed(e);
94       }
95
96       @Override
97       public void mouseDragged(MouseEvent e)
98       {
99         sliderDragging=true;
100         super.mouseDragged(e);
101       }
102       public void mouseReleased(MouseEvent evt)
103       {
104         if (sliderDragging)
105         {
106           sliderDragging=false;
107           valueChanged(true);
108         }
109         ap.paintAlignment(true);
110       }
111     });
112
113     if (av.getAlignment().getAlignmentAnnotation() == null)
114     {
115       return;
116     }
117
118     // Always get default shading from preferences.
119     setDefaultMinMax();
120
121     if (oldcs instanceof AnnotationColourGradient)
122     {
123       AnnotationColourGradient acg = (AnnotationColourGradient) oldcs;
124       currentColours.setSelected(acg.predefinedColours);
125       if (!acg.predefinedColours)
126       {
127         minColour.setBackground(acg.getMinColour());
128         maxColour.setBackground(acg.getMaxColour());
129       }
130       seqAssociated.setSelected(acg.isSeqAssociated());
131     }
132     adjusting = true;
133     annotations = new JComboBox(
134             getAnnotationItems(seqAssociated.isSelected()));
135
136     threshold.addItem("No Threshold");
137     threshold.addItem("Above Threshold");
138     threshold.addItem("Below Threshold");
139
140     if (oldcs instanceof AnnotationColourGradient)
141     {
142       AnnotationColourGradient acg = (AnnotationColourGradient) oldcs;
143       annotations.setSelectedItem(acg.getAnnotation());
144       switch (acg.getAboveThreshold())
145       {
146       case AnnotationColourGradient.NO_THRESHOLD:
147         threshold.setSelectedItem("No Threshold");
148         break;
149       case AnnotationColourGradient.ABOVE_THRESHOLD:
150         threshold.setSelectedItem("Above Threshold");
151         break;
152       case AnnotationColourGradient.BELOW_THRESHOLD:
153         threshold.setSelectedItem("Below Threshold");
154         break;
155       default:
156         throw new Error(
157                 "Implementation error: don't know about threshold setting for current AnnotationColourGradient.");
158       }
159       thresholdIsMin.setSelected(acg.thresholdIsMinMax);
160       thresholdValue.setText("" + acg.getAnnotationThreshold());
161     }
162
163     try
164     {
165       jbInit();
166     } catch (Exception ex)
167     {
168     }
169
170     adjusting = false;
171
172     changeColour();
173     frame.invalidate();
174     frame.pack();
175
176   }
177
178   private Vector<String> getAnnotationItems(boolean isSeqAssociated)
179   {
180     Vector<String> list = new Vector<String>();
181     int index = 1;
182     int[] anmap = new int[av.getAlignment().getAlignmentAnnotation().length];
183     boolean enableSeqAss = false;
184     for (int i = 0; i < av.getAlignment().getAlignmentAnnotation().length; i++)
185     {
186       if (av.getAlignment().getAlignmentAnnotation()[i].sequenceRef == null)
187       {
188         if (isSeqAssociated)
189         {
190           continue;
191         }
192       }
193       else
194       {
195         enableSeqAss = true;
196       }
197       String label = av.getAlignment().getAlignmentAnnotation()[i].label;
198       if (!list.contains(label))
199       {
200         anmap[list.size()] = i;
201         list.addElement(label);
202
203       }
204       else
205       {
206         if (!isSeqAssociated)
207         {
208           anmap[list.size()] = i;
209           list.addElement(label + "_" + (index++));
210         }
211       }
212     }
213     seqAssociated.setEnabled(enableSeqAss);
214     annmap = new int[list.size()];
215     System.arraycopy(anmap, 0, annmap, 0, annmap.length);
216     return list;
217   }
218   private void setDefaultMinMax()
219   {
220     minColour.setBackground(Cache.getDefaultColour("ANNOTATIONCOLOUR_MIN",
221             Color.orange));
222     maxColour.setBackground(Cache.getDefaultColour("ANNOTATIONCOLOUR_MAX",
223             Color.red));
224   }
225
226   public AnnotationColourChooser()
227   {
228     try
229     {
230       jbInit();
231     } catch (Exception ex)
232     {
233       ex.printStackTrace();
234     }
235   }
236
237   private void jbInit() throws Exception
238   {
239     minColour.setFont(JvSwingUtils.getLabelFont());
240     minColour.setBorder(BorderFactory.createEtchedBorder());
241     minColour.setPreferredSize(new Dimension(40, 20));
242     minColour.setToolTipText("Minimum Colour");
243     minColour.addMouseListener(new MouseAdapter()
244     {
245       public void mousePressed(MouseEvent e)
246       {
247         if (minColour.isEnabled())
248         {
249           minColour_actionPerformed();
250         }
251       }
252     });
253     maxColour.setFont(JvSwingUtils.getLabelFont());
254     maxColour.setBorder(BorderFactory.createEtchedBorder());
255     maxColour.setPreferredSize(new Dimension(40, 20));
256     maxColour.setToolTipText("Maximum Colour");
257     maxColour.addMouseListener(new MouseAdapter()
258     {
259       public void mousePressed(MouseEvent e)
260       {
261         if (maxColour.isEnabled())
262         {
263           maxColour_actionPerformed();
264         }
265       }
266     });
267     ok.setOpaque(false);
268     ok.setText("OK");
269     ok.addActionListener(new ActionListener()
270     {
271       public void actionPerformed(ActionEvent e)
272       {
273         ok_actionPerformed(e);
274       }
275     });
276     cancel.setOpaque(false);
277     cancel.setText("Cancel");
278     cancel.addActionListener(new ActionListener()
279     {
280       public void actionPerformed(ActionEvent e)
281       {
282         cancel_actionPerformed(e);
283       }
284     });
285     defColours.setOpaque(false);
286     defColours.setText("Defaults");
287     defColours
288             .setToolTipText("Reset min and max colours to defaults from user preferences.");
289     defColours.addActionListener(new ActionListener()
290     {
291
292       @Override
293       public void actionPerformed(ActionEvent arg0)
294       {
295         resetColours_actionPerformed(arg0);
296       }
297     });
298
299     annotations.addActionListener(new ActionListener()
300     {
301       public void actionPerformed(ActionEvent e)
302       {
303         annotations_actionPerformed(e);
304       }
305     });
306     threshold.addActionListener(new ActionListener()
307     {
308       public void actionPerformed(ActionEvent e)
309       {
310         threshold_actionPerformed(e);
311       }
312     });
313     thresholdValue.addActionListener(new ActionListener()
314     {
315       public void actionPerformed(ActionEvent e)
316       {
317         thresholdValue_actionPerformed(e);
318       }
319     });
320     slider.setPaintLabels(false);
321     slider.setPaintTicks(true);
322     slider.setBackground(Color.white);
323     slider.setEnabled(false);
324     slider.setOpaque(false);
325     slider.setPreferredSize(new Dimension(100, 32));
326     thresholdValue.setEnabled(false);
327     thresholdValue.setColumns(7);
328     currentColours.setFont(JvSwingUtils.getLabelFont());
329     currentColours.setOpaque(false);
330     currentColours.setText("Use Original Colours");
331     currentColours.addActionListener(new ActionListener()
332     {
333       public void actionPerformed(ActionEvent e)
334       {
335         currentColours_actionPerformed(e);
336       }
337     });
338     thresholdIsMin.setBackground(Color.white);
339     thresholdIsMin.setFont(JvSwingUtils.getLabelFont());
340     thresholdIsMin.setText("Threshold is Min/Max");
341     thresholdIsMin.addActionListener(new ActionListener()
342     {
343       public void actionPerformed(ActionEvent actionEvent)
344       {
345         thresholdIsMin_actionPerformed(actionEvent);
346       }
347     });
348     seqAssociated.setBackground(Color.white);
349     seqAssociated.setFont(JvSwingUtils.getLabelFont());
350     seqAssociated.setText("Per-sequence only");
351     seqAssociated.addActionListener(new ActionListener()
352     {
353
354       @Override
355       public void actionPerformed(ActionEvent arg0)
356       {
357         seqAssociated_actionPerformed(arg0);
358       }
359     });
360
361     this.setLayout(borderLayout1);
362     jPanel2.setLayout(new MigLayout("", "[left][center][right]", "[][][]"));
363     jPanel1.setBackground(Color.white);
364     jPanel2.setBackground(Color.white);
365
366     jPanel1.add(ok);
367     jPanel1.add(cancel);
368     jPanel2.add(annotations, "grow, wrap");
369     jPanel2.add(seqAssociated);
370     jPanel2.add(currentColours);
371     JPanel colpanel = new JPanel(new FlowLayout());
372     colpanel.setBackground(Color.white);
373     colpanel.add(minColour);
374     colpanel.add(maxColour);
375     jPanel2.add(colpanel, "wrap");
376     jPanel2.add(threshold);
377     jPanel2.add(defColours, "skip 1, wrap");
378     jPanel2.add(thresholdIsMin);
379     jPanel2.add(slider, "grow");
380     jPanel2.add(thresholdValue, "grow");
381     this.add(jPanel1, java.awt.BorderLayout.SOUTH);
382     this.add(jPanel2, java.awt.BorderLayout.CENTER);
383     this.validate();
384   }
385
386   protected void seqAssociated_actionPerformed(ActionEvent arg0)
387   {
388     adjusting = true;
389     String cursel = (String) annotations.getSelectedItem();
390     boolean isvalid = false, isseqs = seqAssociated.isSelected();
391     this.annotations.removeAllItems();
392     for (String anitem : getAnnotationItems(seqAssociated.isSelected()))
393     {
394       if (anitem.equals(cursel) || (isseqs && cursel.startsWith(anitem)))
395       {
396         isvalid = true;
397         cursel = anitem;
398       }
399       this.annotations.addItem(anitem);
400     }
401     adjusting = false;
402     if (isvalid)
403     {
404       this.annotations.setSelectedItem(cursel);
405     }
406     else
407     {
408       if (annotations.getItemCount() > 0)
409       {
410         annotations.setSelectedIndex(0);
411       }
412     }
413   }
414
415   protected void resetColours_actionPerformed(ActionEvent arg0)
416   {
417     setDefaultMinMax();
418     changeColour();
419   }
420
421   JComboBox annotations;
422
423   int[] annmap;
424
425   JPanel minColour = new JPanel();
426
427   JPanel maxColour = new JPanel();
428
429   JButton defColours = new JButton();
430
431   JButton ok = new JButton();
432
433   JButton cancel = new JButton();
434
435   JPanel jPanel1 = new JPanel();
436
437   JPanel jPanel2 = new JPanel();
438
439   BorderLayout borderLayout1 = new BorderLayout();
440
441   JComboBox threshold = new JComboBox();
442
443   JSlider slider = new JSlider();
444
445   JTextField thresholdValue = new JTextField(20);
446
447   JCheckBox currentColours = new JCheckBox();
448
449   JCheckBox thresholdIsMin = new JCheckBox();
450
451   JCheckBox seqAssociated = new JCheckBox();
452
453   public void minColour_actionPerformed()
454   {
455     Color col = JColorChooser.showDialog(this,
456             "Select Colour for Minimum Value", minColour.getBackground());
457     if (col != null)
458     {
459       minColour.setBackground(col);
460     }
461     minColour.repaint();
462     changeColour();
463   }
464
465   public void maxColour_actionPerformed()
466   {
467     Color col = JColorChooser.showDialog(this,
468             "Select Colour for Maximum Value", maxColour.getBackground());
469     if (col != null)
470     {
471       maxColour.setBackground(col);
472     }
473     maxColour.repaint();
474     changeColour();
475   }
476
477   void changeColour()
478   {
479     // Check if combobox is still adjusting
480     if (adjusting)
481     {
482       return;
483     }
484
485     currentAnnotation = av.getAlignment().getAlignmentAnnotation()[annmap[annotations
486             .getSelectedIndex()]];
487
488     int aboveThreshold = -1;
489     if (threshold.getSelectedItem().equals("Above Threshold"))
490     {
491       aboveThreshold = AnnotationColourGradient.ABOVE_THRESHOLD;
492     }
493     else if (threshold.getSelectedItem().equals("Below Threshold"))
494     {
495       aboveThreshold = AnnotationColourGradient.BELOW_THRESHOLD;
496     }
497
498     slider.setEnabled(true);
499     thresholdValue.setEnabled(true);
500     thresholdIsMin.setEnabled(true);
501
502     if (aboveThreshold == AnnotationColourGradient.NO_THRESHOLD)
503     {
504       slider.setEnabled(false);
505       thresholdValue.setEnabled(false);
506       thresholdValue.setText("");
507       thresholdIsMin.setEnabled(false);
508     }
509     else if (aboveThreshold != AnnotationColourGradient.NO_THRESHOLD
510             && currentAnnotation.threshold == null)
511     {
512       currentAnnotation
513               .setThreshold(new jalview.datamodel.GraphLine(
514                       (currentAnnotation.graphMax - currentAnnotation.graphMin) / 2f,
515                       "Threshold", Color.black));
516     }
517
518     if (aboveThreshold != AnnotationColourGradient.NO_THRESHOLD)
519     {
520       adjusting = true;
521       float range = currentAnnotation.graphMax * 1000
522               - currentAnnotation.graphMin * 1000;
523
524       slider.setMinimum((int) (currentAnnotation.graphMin * 1000));
525       slider.setMaximum((int) (currentAnnotation.graphMax * 1000));
526       slider.setValue((int) (currentAnnotation.threshold.value * 1000));
527       thresholdValue.setText(currentAnnotation.threshold.value + "");
528       slider.setMajorTickSpacing((int) (range / 10f));
529       slider.setEnabled(true);
530       thresholdValue.setEnabled(true);
531       adjusting = false;
532     }
533
534     AnnotationColourGradient acg = null;
535     if (currentColours.isSelected())
536     {
537       acg = new AnnotationColourGradient(currentAnnotation,
538               av.getGlobalColourScheme(), aboveThreshold);
539     }
540     else
541     {
542       acg = new AnnotationColourGradient(currentAnnotation,
543               minColour.getBackground(), maxColour.getBackground(),
544               aboveThreshold);
545     }
546     acg.setSeqAssociated(seqAssociated.isSelected());
547
548     if (currentAnnotation.graphMin == 0f
549             && currentAnnotation.graphMax == 0f)
550     {
551       acg.predefinedColours = true;
552     }
553
554     acg.thresholdIsMinMax = thresholdIsMin.isSelected();
555
556     av.setGlobalColourScheme(acg);
557
558     if (av.getAlignment().getGroups() != null)
559     {
560
561       for (SequenceGroup sg : ap.av.getAlignment().getGroups())
562       {
563         if (sg.cs == null)
564         {
565           continue;
566         }
567
568         if (currentColours.isSelected())
569         {
570           sg.cs = new AnnotationColourGradient(currentAnnotation, sg.cs,
571                   aboveThreshold);
572           ((AnnotationColourGradient) sg.cs).setSeqAssociated(seqAssociated
573                   .isSelected());
574
575         }
576         else
577         {
578           sg.cs = new AnnotationColourGradient(currentAnnotation,
579                   minColour.getBackground(), maxColour.getBackground(),
580                   aboveThreshold);
581           ((AnnotationColourGradient) sg.cs).setSeqAssociated(seqAssociated
582                   .isSelected());
583         }
584
585       }
586     }
587     ap.alignmentChanged();
588     // ensure all associated views (overviews, structures, etc) are notified of
589     // updated colours.
590     ap.paintAlignment(true);
591   }
592
593   public void ok_actionPerformed(ActionEvent e)
594   {
595     changeColour();
596     try
597     {
598       frame.setClosed(true);
599     } catch (Exception ex)
600     {
601     }
602   }
603
604   public void cancel_actionPerformed(ActionEvent e)
605   {
606     reset();
607     // ensure all original colouring is propagated to listeners.
608     ap.paintAlignment(true);
609     try
610     {
611       frame.setClosed(true);
612     } catch (Exception ex)
613     {
614     }
615   }
616
617   void reset()
618   {
619     av.setGlobalColourScheme(oldcs);
620     if (av.getAlignment().getGroups() != null)
621     {
622
623       for (SequenceGroup sg : ap.av.getAlignment().getGroups())
624       {
625         sg.cs = (ColourSchemeI) oldgroupColours.get(sg);
626       }
627     }
628   }
629
630   public void thresholdCheck_actionPerformed(ActionEvent e)
631   {
632     changeColour();
633   }
634
635   public void annotations_actionPerformed(ActionEvent e)
636   {
637     changeColour();
638   }
639
640   public void threshold_actionPerformed(ActionEvent e)
641   {
642     changeColour();
643   }
644
645   public void thresholdValue_actionPerformed(ActionEvent e)
646   {
647     try
648     {
649       float f = Float.parseFloat(thresholdValue.getText());
650       slider.setValue((int) (f * 1000));
651     } catch (NumberFormatException ex)
652     {
653     }
654   }
655
656   public void valueChanged(boolean updateAllAnnotation)
657   {
658     if (currentColours.isSelected()
659             && !(av.getGlobalColourScheme() instanceof AnnotationColourGradient))
660     {
661       changeColour();
662     }
663     currentAnnotation.threshold.value = (float) slider.getValue() / 1000f;
664     propagateSeqAssociatedThreshold(updateAllAnnotation);
665     ap.paintAlignment(false);
666   }
667
668   private void propagateSeqAssociatedThreshold(boolean allAnnotation)
669   {
670     if (currentAnnotation.sequenceRef == null
671             || currentAnnotation.threshold == null)
672     {
673       return;
674     }
675     // TODO: JAL-1327 only update visible annotation thresholds if allAnnotation is false, since we only need to provide a quick visual indicator
676
677     float thr = currentAnnotation.threshold.value;
678     for (int i = 0; i < av.getAlignment().getAlignmentAnnotation().length; i++)
679     {
680       AlignmentAnnotation aa = av.getAlignment().getAlignmentAnnotation()[i];
681       if (aa.label.equals(currentAnnotation.label))
682       {
683         aa.threshold.value = thr;
684       }
685     }
686   }
687
688   public void currentColours_actionPerformed(ActionEvent e)
689   {
690     if (currentColours.isSelected())
691     {
692       reset();
693     }
694
695     maxColour.setEnabled(!currentColours.isSelected());
696     minColour.setEnabled(!currentColours.isSelected());
697
698     changeColour();
699   }
700
701   public void thresholdIsMin_actionPerformed(ActionEvent actionEvent)
702   {
703     changeColour();
704   }
705
706 }