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