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