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