Merge branch 'releases/Release_2_11_3_Branch'
[jalview.git] / src / jalview / gui / AnnotationColourChooser.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ 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 java.awt.BorderLayout;
24 import java.awt.Color;
25 import java.awt.Dimension;
26 import java.awt.FlowLayout;
27 import java.awt.event.ActionEvent;
28 import java.awt.event.ActionListener;
29 import java.awt.event.MouseAdapter;
30 import java.awt.event.MouseEvent;
31 import java.util.Hashtable;
32 import java.util.Vector;
33
34 import javax.swing.BorderFactory;
35 import javax.swing.JButton;
36 import javax.swing.JCheckBox;
37 import javax.swing.JComboBox;
38 import javax.swing.JInternalFrame;
39 import javax.swing.JLayeredPane;
40 import javax.swing.JPanel;
41
42 import jalview.bin.Cache;
43 import jalview.datamodel.AlignmentAnnotation;
44 import jalview.datamodel.Annotation;
45 import jalview.datamodel.GraphLine;
46 import jalview.datamodel.SequenceGroup;
47 import jalview.gui.JalviewColourChooser.ColourChooserListener;
48 import jalview.schemes.AnnotationColourGradient;
49 import jalview.schemes.ColourSchemeI;
50 import jalview.util.MessageManager;
51 import net.miginfocom.swing.MigLayout;
52
53 @SuppressWarnings("serial")
54 public class AnnotationColourChooser extends AnnotationRowFilter
55 {
56   private ColourSchemeI oldcs;
57
58   private JButton defColours;
59
60   private Hashtable<SequenceGroup, ColourSchemeI> oldgroupColours;
61
62   private JCheckBox useOriginalColours = new JCheckBox();
63
64   JPanel minColour = new JPanel();
65
66   JPanel maxColour = new JPanel();
67
68   private JCheckBox thresholdIsMin = new JCheckBox();
69
70   protected static final int MIN_WIDTH = 500;
71
72   protected static final int MIN_HEIGHT = 240;
73
74   public AnnotationColourChooser(AlignViewport av, final AlignmentPanel ap)
75   {
76     this(av, ap, null);
77   }
78
79   public AnnotationColourChooser(AlignViewport av, final AlignmentPanel ap,
80           AnnotationColourGradient initSettings)
81   {
82     super(av, ap);
83     oldcs = av.getGlobalColourScheme();
84     if (av.getAlignment().getGroups() != null)
85     {
86       oldgroupColours = new Hashtable<>();
87       for (SequenceGroup sg : ap.av.getAlignment().getGroups())
88       {
89         if (sg.getColourScheme() != null)
90         {
91           oldgroupColours.put(sg, sg.getColourScheme());
92         }
93       }
94     }
95     frame = new JInternalFrame();
96     frame.setFrameIcon(null);
97     frame.setContentPane(this);
98     frame.setLayer(JLayeredPane.PALETTE_LAYER);
99     Desktop.addInternalFrame(frame,
100             MessageManager.getString("label.colour_by_annotation"), 520,
101             215);
102     frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
103     addSliderChangeListener();
104     addSliderMouseListeners();
105
106     if (av.getAlignment().getAlignmentAnnotation() == null)
107     {
108       return;
109     }
110
111     // Always get default shading from preferences.
112     setDefaultMinMax();
113
114     adjusting = true;
115     if (oldcs instanceof AnnotationColourGradient && initSettings == null)
116     {
117       // init from oldcs
118       initialiseFrom((AnnotationColourGradient) oldcs);
119     }
120     else
121     {
122       // use initial colour gradient - if any..
123       initialiseFrom(initSettings);
124     }
125
126     jbInit();
127     adjusting = false;
128
129     updateView();
130     frame.invalidate();
131     frame.pack();
132   }
133
134   private void initialiseFrom(AnnotationColourGradient acg)
135   {
136     if (acg != null)
137     {
138       useOriginalColours.setSelected(
139               acg.isPredefinedColours() || acg.getBaseColour() != null);
140       if (!acg.isPredefinedColours() && acg.getBaseColour() == null)
141       {
142         minColour.setBackground(acg.getMinColour());
143         maxColour.setBackground(acg.getMaxColour());
144       }
145       seqAssociated.setSelected(acg.isSeqAssociated());
146
147     }
148     Vector<String> annotItems = getAnnotationItems(
149             seqAssociated.isSelected());
150     annotations = new JComboBox<>(annotItems);
151
152     populateThresholdComboBox(threshold);
153
154     if (acg != null)
155     {
156       String label = getAnnotationMenuLabel(acg.getAnnotation());
157       // TODO: workaround below shouldn't be necessary - there's a bug in
158       // getAnnotationMenuLabel!
159       if (acg.isSeqAssociated())
160       {
161         label = acg.getAnnotation().label;
162       }
163       annotations.setSelectedItem(label);
164       switch (acg.getAboveThreshold())
165       {
166       case AnnotationColourGradient.NO_THRESHOLD:
167         getThreshold().setSelectedIndex(0);
168         break;
169       case AnnotationColourGradient.ABOVE_THRESHOLD:
170         getThreshold().setSelectedIndex(1);
171         break;
172       case AnnotationColourGradient.BELOW_THRESHOLD:
173         getThreshold().setSelectedIndex(2);
174         break;
175       default:
176         throw new Error(MessageManager.getString(
177                 "error.implementation_error_dont_know_about_threshold_setting"));
178       }
179       thresholdIsMin.setSelected(acg.isThresholdIsMinMax());
180       thresholdValue.setText(String.valueOf(acg.getAnnotationThreshold()));
181     }
182   }
183
184   @Override
185   protected void jbInit()
186   {
187     super.jbInit();
188
189     minColour.setFont(JvSwingUtils.getLabelFont());
190     minColour.setBorder(BorderFactory.createEtchedBorder());
191     minColour.setPreferredSize(new Dimension(40, 20));
192     minColour.setToolTipText(MessageManager.getString("label.min_colour"));
193     minColour.addMouseListener(new MouseAdapter()
194     {
195       @Override
196       public void mousePressed(MouseEvent e)
197       {
198         if (minColour.isEnabled())
199         {
200           showColourChooser(minColour, "label.select_colour_minimum_value");
201         }
202       }
203     });
204     maxColour.setFont(JvSwingUtils.getLabelFont());
205     maxColour.setBorder(BorderFactory.createEtchedBorder());
206     maxColour.setPreferredSize(new Dimension(40, 20));
207     maxColour.setToolTipText(MessageManager.getString("label.max_colour"));
208     maxColour.addMouseListener(new MouseAdapter()
209     {
210       @Override
211       public void mousePressed(MouseEvent e)
212       {
213         if (maxColour.isEnabled())
214         {
215           showColourChooser(maxColour, "label.select_colour_maximum_value");
216         }
217       }
218     });
219
220     defColours = new JButton();
221     defColours.setOpaque(false);
222     defColours.setText(MessageManager.getString("action.set_defaults"));
223     defColours.setToolTipText(MessageManager
224             .getString("label.reset_min_max_colours_to_defaults"));
225     defColours.addActionListener(new ActionListener()
226     {
227
228       @Override
229       public void actionPerformed(ActionEvent arg0)
230       {
231         resetColours_actionPerformed();
232       }
233     });
234
235     useOriginalColours.setFont(JvSwingUtils.getLabelFont());
236     useOriginalColours.setOpaque(false);
237     useOriginalColours.setText(
238             MessageManager.getString("label.use_original_colours"));
239     useOriginalColours.addActionListener(new ActionListener()
240     {
241       @Override
242       public void actionPerformed(ActionEvent e)
243       {
244         originalColours_actionPerformed();
245       }
246     });
247     thresholdIsMin.setBackground(Color.white);
248     thresholdIsMin.setFont(JvSwingUtils.getLabelFont());
249     thresholdIsMin
250             .setText(MessageManager.getString("label.threshold_minmax"));
251     thresholdIsMin.addActionListener(new ActionListener()
252     {
253       @Override
254       public void actionPerformed(ActionEvent actionEvent)
255       {
256         thresholdIsMin_actionPerformed();
257       }
258     });
259     seqAssociated.setBackground(Color.white);
260     seqAssociated.setFont(JvSwingUtils.getLabelFont());
261     seqAssociated
262             .setText(MessageManager.getString("label.per_sequence_only"));
263     seqAssociated.addActionListener(new ActionListener()
264     {
265
266       @Override
267       public void actionPerformed(ActionEvent arg0)
268       {
269         seqAssociated_actionPerformed(annotations);
270       }
271     });
272
273     this.setLayout(new BorderLayout());
274     JPanel jPanel1 = new JPanel();
275     JPanel jPanel2 = new JPanel();
276     jPanel2.setLayout(new MigLayout("", "[left][center][right]", "[][][]"));
277     jPanel1.setBackground(Color.white);
278     jPanel2.setBackground(Color.white);
279
280     jPanel1.add(ok);
281     jPanel1.add(cancel);
282     jPanel2.add(annotations, "grow, wrap");
283     jPanel2.add(seqAssociated);
284     jPanel2.add(useOriginalColours);
285     JPanel colpanel = new JPanel(new FlowLayout());
286     colpanel.setBackground(Color.white);
287     colpanel.add(minColour);
288     colpanel.add(maxColour);
289     jPanel2.add(colpanel, "wrap");
290     jPanel2.add(getThreshold());
291     jPanel2.add(defColours, "skip 1, wrap");
292     jPanel2.add(thresholdIsMin);
293     jPanel2.add(slider, "grow");
294     jPanel2.add(thresholdValue, "grow");
295     this.add(jPanel1, java.awt.BorderLayout.SOUTH);
296     this.add(jPanel2, java.awt.BorderLayout.CENTER);
297     this.validate();
298   }
299
300   protected void resetColours_actionPerformed()
301   {
302     setDefaultMinMax();
303     updateView();
304   }
305
306   private void setDefaultMinMax()
307   {
308     minColour.setBackground(
309             Cache.getDefaultColour("ANNOTATIONCOLOUR_MIN", Color.orange));
310     maxColour.setBackground(
311             Cache.getDefaultColour("ANNOTATIONCOLOUR_MAX", Color.red));
312   }
313
314   protected void showColourChooser(JPanel colourPanel, String titleKey)
315   {
316     String ttl = MessageManager.getString(titleKey);
317     ColourChooserListener listener = new ColourChooserListener()
318     {
319       @Override
320       public void colourSelected(Color c)
321       {
322         colourPanel.setBackground(c);
323         colourPanel.repaint();
324         updateView();
325       }
326     };
327     JalviewColourChooser.showColourChooser(Desktop.getDesktop(), ttl,
328             colourPanel.getBackground(), listener);
329   }
330
331   @Override
332   public void reset()
333   {
334     this.ap.alignFrame.changeColour(oldcs);
335     if (av.getAlignment().getGroups() != null)
336     {
337
338       for (SequenceGroup sg : ap.av.getAlignment().getGroups())
339       {
340         sg.setColourScheme(oldgroupColours.get(sg));
341       }
342     }
343   }
344
345   @Override
346   public void valueChanged(boolean updateAllAnnotation)
347   {
348     if (slider.isEnabled())
349     {
350       if (useOriginalColours.isSelected() && !(av
351               .getGlobalColourScheme() instanceof AnnotationColourGradient))
352       {
353         updateView();
354       }
355       getCurrentAnnotation().threshold.value = getSliderValue();
356       propagateSeqAssociatedThreshold(updateAllAnnotation,
357               getCurrentAnnotation());
358       ap.paintAlignment(false, false);
359     }
360   }
361
362   public void originalColours_actionPerformed()
363   {
364     boolean selected = useOriginalColours.isSelected();
365     if (selected)
366     {
367       reset();
368     }
369     maxColour.setEnabled(!selected);
370     minColour.setEnabled(!selected);
371     thresholdIsMin.setEnabled(!selected);
372     updateView();
373   }
374
375   @Override
376   public void updateView()
377   {
378     // Check if combobox is still adjusting
379     if (adjusting)
380     {
381       return;
382     }
383
384     int selIndex = annotations.getSelectedIndex();
385     int annIndex = annmap[selIndex];
386     setCurrentAnnotation(
387             av.getAlignment().getAlignmentAnnotation()[annIndex]);
388
389     int selectedThresholdItem = getSelectedThresholdItem(
390             getThreshold().getSelectedIndex());
391
392     slider.setEnabled(true);
393     thresholdValue.setEnabled(true);
394     thresholdIsMin.setEnabled(!useOriginalColours.isSelected());
395
396     final AlignmentAnnotation currentAnnotation = getCurrentAnnotation();
397     if (selectedThresholdItem == AnnotationColourGradient.NO_THRESHOLD)
398     {
399       slider.setEnabled(false);
400       thresholdValue.setEnabled(false);
401       thresholdValue.setText("");
402       thresholdIsMin.setEnabled(false);
403     }
404     else if (selectedThresholdItem != AnnotationColourGradient.NO_THRESHOLD
405             && currentAnnotation.threshold == null)
406     {
407       currentAnnotation.setThreshold(new GraphLine(
408               (currentAnnotation.graphMax - currentAnnotation.graphMin)
409                       / 2f,
410               "Threshold", Color.black));
411     }
412
413     if (selectedThresholdItem != AnnotationColourGradient.NO_THRESHOLD)
414     {
415       adjusting = true;
416       setSliderModel(currentAnnotation.graphMin, currentAnnotation.graphMax,
417               currentAnnotation.threshold.value);
418       slider.setEnabled(true);
419
420       setThresholdValueText();
421       thresholdValue.setEnabled(true);
422       adjusting = false;
423     }
424     colorAlignmentContaining(currentAnnotation, selectedThresholdItem);
425
426     ap.alignmentChanged();
427   }
428
429   protected void colorAlignmentContaining(AlignmentAnnotation currentAnn,
430           int selectedThresholdOption)
431   {
432
433     AnnotationColourGradient acg = null;
434     if (useOriginalColours.isSelected())
435     {
436       acg = new AnnotationColourGradient(currentAnn,
437               av.getGlobalColourScheme(), selectedThresholdOption);
438     }
439     else
440     {
441       acg = new AnnotationColourGradient(currentAnn,
442               minColour.getBackground(), maxColour.getBackground(),
443               selectedThresholdOption);
444     }
445     acg.setSeqAssociated(seqAssociated.isSelected());
446
447     if (currentAnn.graphMin == 0f && currentAnn.graphMax == 0f)
448     {
449       acg.setPredefinedColours(true);
450     }
451
452     acg.setThresholdIsMinMax(thresholdIsMin.isSelected());
453
454     this.ap.alignFrame.changeColour(acg);
455
456     if (av.getAlignment().getGroups() != null)
457     {
458
459       for (SequenceGroup sg : ap.av.getAlignment().getGroups())
460       {
461         if (sg.cs == null)
462         {
463           continue;
464         }
465         sg.setColourScheme(acg.getInstance(av, sg));
466       }
467     }
468   }
469
470   @Override
471   protected void sliderDragReleased()
472   {
473     super.sliderDragReleased();
474     ap.paintAlignment(true, true);
475   }
476
477   /**
478    * construct and display a colourchooser for a given annotation row
479    * 
480    * @param av
481    * @param ap
482    * @param alignmentAnnotation
483    * @param perseq
484    *          - when true, enable per-sequence if alignment annotation is per
485    *          sequence
486    */
487   public static void displayFor(AlignViewport av, AlignmentPanel ap,
488           AlignmentAnnotation alignmentAnnotation, boolean perSeq)
489   {
490     ColourSchemeI global = av.getGlobalColourScheme();
491     AnnotationColourGradient newCS = new AnnotationColourGradient(
492             alignmentAnnotation, global,
493             alignmentAnnotation.threshold != null
494                     ? AnnotationColourGradient.ABOVE_THRESHOLD
495                     : AnnotationColourGradient.NO_THRESHOLD);
496     if (alignmentAnnotation.sequenceRef != null)
497     {
498       newCS.setSeqAssociated(perSeq);
499     }
500     for (int i = 0; i < alignmentAnnotation.annotations.length; i++)
501     {
502       Annotation ann = alignmentAnnotation.annotations[i];
503       if (ann != null && ann.colour != null
504               && !ann.colour.equals(Color.white))
505       {
506         newCS.setPredefinedColours(true);
507         break;
508       }
509     }
510     AnnotationColourChooser achooser = new AnnotationColourChooser(av, ap,
511             newCS);
512   }
513 }