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