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