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