JAL-1355
[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(MessageManager.getString("error.implementation_error_dont_know_about_thereshold_setting"));
172       }
173       thresholdIsMin.setSelected(acg.thresholdIsMinMax);
174       thresholdValue.setText("" + acg.getAnnotationThreshold());
175     }
176
177     try
178     {
179       jbInit();
180     } catch (Exception ex)
181     {
182     }
183
184     adjusting = false;
185
186     changeColour();
187     frame.invalidate();
188     frame.pack();
189
190   }
191
192   private Vector<String> getAnnotationItems(boolean isSeqAssociated)
193   {
194     Vector<String> list = new Vector<String>();
195     int index = 1;
196     int[] anmap = new int[av.getAlignment().getAlignmentAnnotation().length];
197     boolean enableSeqAss = false;
198     for (int i = 0; i < av.getAlignment().getAlignmentAnnotation().length; i++)
199     {
200       if (av.getAlignment().getAlignmentAnnotation()[i].sequenceRef == null)
201       {
202         if (isSeqAssociated)
203         {
204           continue;
205         }
206       }
207       else
208       {
209         enableSeqAss = true;
210       }
211       String label = av.getAlignment().getAlignmentAnnotation()[i].label;
212       if (!list.contains(label))
213       {
214         anmap[list.size()] = i;
215         list.addElement(label);
216
217       }
218       else
219       {
220         if (!isSeqAssociated)
221         {
222           anmap[list.size()] = i;
223           list.addElement(label + "_" + (index++));
224         }
225       }
226     }
227     seqAssociated.setEnabled(enableSeqAss);
228     annmap = new int[list.size()];
229     System.arraycopy(anmap, 0, annmap, 0, annmap.length);
230     return list;
231   }
232
233   private void setDefaultMinMax()
234   {
235     minColour.setBackground(Cache.getDefaultColour("ANNOTATIONCOLOUR_MIN",
236             Color.orange));
237     maxColour.setBackground(Cache.getDefaultColour("ANNOTATIONCOLOUR_MAX",
238             Color.red));
239   }
240
241   public AnnotationColourChooser()
242   {
243     try
244     {
245       jbInit();
246     } catch (Exception ex)
247     {
248       ex.printStackTrace();
249     }
250   }
251
252   private void jbInit() throws Exception
253   {
254     minColour.setFont(JvSwingUtils.getLabelFont());
255     minColour.setBorder(BorderFactory.createEtchedBorder());
256     minColour.setPreferredSize(new Dimension(40, 20));
257     minColour.setToolTipText(MessageManager.getString("label.min_colour"));
258     minColour.addMouseListener(new MouseAdapter()
259     {
260       public void mousePressed(MouseEvent e)
261       {
262         if (minColour.isEnabled())
263         {
264           minColour_actionPerformed();
265         }
266       }
267     });
268     maxColour.setFont(JvSwingUtils.getLabelFont());
269     maxColour.setBorder(BorderFactory.createEtchedBorder());
270     maxColour.setPreferredSize(new Dimension(40, 20));
271     maxColour.setToolTipText(MessageManager.getString("label.max_colour"));
272     maxColour.addMouseListener(new MouseAdapter()
273     {
274       public void mousePressed(MouseEvent e)
275       {
276         if (maxColour.isEnabled())
277         {
278           maxColour_actionPerformed();
279         }
280       }
281     });
282     ok.setOpaque(false);
283     ok.setText(MessageManager.getString("action.ok"));
284     ok.addActionListener(new ActionListener()
285     {
286       public void actionPerformed(ActionEvent e)
287       {
288         ok_actionPerformed(e);
289       }
290     });
291     cancel.setOpaque(false);
292     cancel.setText(MessageManager.getString("action.cancel"));
293     cancel.addActionListener(new ActionListener()
294     {
295       public void actionPerformed(ActionEvent e)
296       {
297         cancel_actionPerformed(e);
298       }
299     });
300     defColours.setOpaque(false);
301     defColours.setText(MessageManager.getString("action.set_defaults"));
302     defColours.setToolTipText(MessageManager
303             .getString("label.reset_min_max_colours_to_defaults"));
304     defColours.addActionListener(new ActionListener()
305     {
306
307       @Override
308       public void actionPerformed(ActionEvent arg0)
309       {
310         resetColours_actionPerformed(arg0);
311       }
312     });
313
314     annotations.addActionListener(new ActionListener()
315     {
316       public void actionPerformed(ActionEvent e)
317       {
318         annotations_actionPerformed(e);
319       }
320     });
321     threshold.addActionListener(new ActionListener()
322     {
323       public void actionPerformed(ActionEvent e)
324       {
325         threshold_actionPerformed(e);
326       }
327     });
328     thresholdValue.addActionListener(new ActionListener()
329     {
330       public void actionPerformed(ActionEvent e)
331       {
332         thresholdValue_actionPerformed(e);
333       }
334     });
335     slider.setPaintLabels(false);
336     slider.setPaintTicks(true);
337     slider.setBackground(Color.white);
338     slider.setEnabled(false);
339     slider.setOpaque(false);
340     slider.setPreferredSize(new Dimension(100, 32));
341     thresholdValue.setEnabled(false);
342     thresholdValue.setColumns(7);
343     currentColours.setFont(JvSwingUtils.getLabelFont());
344     currentColours.setOpaque(false);
345     currentColours.setText(MessageManager
346             .getString("label.use_original_colours"));
347     currentColours.addActionListener(new ActionListener()
348     {
349       public void actionPerformed(ActionEvent e)
350       {
351         currentColours_actionPerformed(e);
352       }
353     });
354     thresholdIsMin.setBackground(Color.white);
355     thresholdIsMin.setFont(JvSwingUtils.getLabelFont());
356     thresholdIsMin.setText(MessageManager
357             .getString("label.threshold_minmax"));
358     thresholdIsMin.addActionListener(new ActionListener()
359     {
360       public void actionPerformed(ActionEvent actionEvent)
361       {
362         thresholdIsMin_actionPerformed(actionEvent);
363       }
364     });
365     seqAssociated.setBackground(Color.white);
366     seqAssociated.setFont(JvSwingUtils.getLabelFont());
367     seqAssociated.setText(MessageManager
368             .getString("label.per_sequence_only"));
369     seqAssociated.addActionListener(new ActionListener()
370     {
371
372       @Override
373       public void actionPerformed(ActionEvent arg0)
374       {
375         seqAssociated_actionPerformed(arg0);
376       }
377     });
378
379     this.setLayout(borderLayout1);
380     jPanel2.setLayout(new MigLayout("", "[left][center][right]", "[][][]"));
381     jPanel1.setBackground(Color.white);
382     jPanel2.setBackground(Color.white);
383
384     jPanel1.add(ok);
385     jPanel1.add(cancel);
386     jPanel2.add(annotations, "grow, wrap");
387     jPanel2.add(seqAssociated);
388     jPanel2.add(currentColours);
389     JPanel colpanel = new JPanel(new FlowLayout());
390     colpanel.setBackground(Color.white);
391     colpanel.add(minColour);
392     colpanel.add(maxColour);
393     jPanel2.add(colpanel, "wrap");
394     jPanel2.add(threshold);
395     jPanel2.add(defColours, "skip 1, wrap");
396     jPanel2.add(thresholdIsMin);
397     jPanel2.add(slider, "grow");
398     jPanel2.add(thresholdValue, "grow");
399     this.add(jPanel1, java.awt.BorderLayout.SOUTH);
400     this.add(jPanel2, java.awt.BorderLayout.CENTER);
401     this.validate();
402   }
403
404   protected void seqAssociated_actionPerformed(ActionEvent arg0)
405   {
406     adjusting = true;
407     String cursel = (String) annotations.getSelectedItem();
408     boolean isvalid = false, isseqs = seqAssociated.isSelected();
409     this.annotations.removeAllItems();
410     for (String anitem : getAnnotationItems(seqAssociated.isSelected()))
411     {
412       if (anitem.equals(cursel) || (isseqs && cursel.startsWith(anitem)))
413       {
414         isvalid = true;
415         cursel = anitem;
416       }
417       this.annotations.addItem(anitem);
418     }
419     adjusting = false;
420     if (isvalid)
421     {
422       this.annotations.setSelectedItem(cursel);
423     }
424     else
425     {
426       if (annotations.getItemCount() > 0)
427       {
428         annotations.setSelectedIndex(0);
429       }
430     }
431   }
432
433   protected void resetColours_actionPerformed(ActionEvent arg0)
434   {
435     setDefaultMinMax();
436     changeColour();
437   }
438
439   JComboBox annotations;
440
441   int[] annmap;
442
443   JPanel minColour = new JPanel();
444
445   JPanel maxColour = new JPanel();
446
447   JButton defColours = new JButton();
448
449   JButton ok = new JButton();
450
451   JButton cancel = new JButton();
452
453   JPanel jPanel1 = new JPanel();
454
455   JPanel jPanel2 = new JPanel();
456
457   BorderLayout borderLayout1 = new BorderLayout();
458
459   JComboBox threshold = new JComboBox();
460
461   JSlider slider = new JSlider();
462
463   JTextField thresholdValue = new JTextField(20);
464
465   JCheckBox currentColours = new JCheckBox();
466
467   JCheckBox thresholdIsMin = new JCheckBox();
468
469   JCheckBox seqAssociated = new JCheckBox();
470
471   public void minColour_actionPerformed()
472   {
473     Color col = JColorChooser.showDialog(this,
474             MessageManager.getString("label.select_colour_minimum_value"), minColour.getBackground());
475     if (col != null)
476     {
477       minColour.setBackground(col);
478     }
479     minColour.repaint();
480     changeColour();
481   }
482
483   public void maxColour_actionPerformed()
484   {
485     Color col = JColorChooser.showDialog(this,
486             MessageManager.getString("label.select_colour_maximum_value"), maxColour.getBackground());
487     if (col != null)
488     {
489       maxColour.setBackground(col);
490     }
491     maxColour.repaint();
492     changeColour();
493   }
494
495   void changeColour()
496   {
497     // Check if combobox is still adjusting
498     if (adjusting)
499     {
500       return;
501     }
502
503     currentAnnotation = av.getAlignment().getAlignmentAnnotation()[annmap[annotations
504             .getSelectedIndex()]];
505
506     int aboveThreshold = -1;
507     if (threshold.getSelectedIndex() == 1)
508     {
509       aboveThreshold = AnnotationColourGradient.ABOVE_THRESHOLD;
510     }
511     else if (threshold.getSelectedIndex() == 2)
512     {
513       aboveThreshold = AnnotationColourGradient.BELOW_THRESHOLD;
514     }
515
516     slider.setEnabled(true);
517     thresholdValue.setEnabled(true);
518     thresholdIsMin.setEnabled(true);
519
520     if (aboveThreshold == AnnotationColourGradient.NO_THRESHOLD)
521     {
522       slider.setEnabled(false);
523       thresholdValue.setEnabled(false);
524       thresholdValue.setText("");
525       thresholdIsMin.setEnabled(false);
526     }
527     else if (aboveThreshold != AnnotationColourGradient.NO_THRESHOLD
528             && currentAnnotation.threshold == null)
529     {
530       currentAnnotation
531               .setThreshold(new jalview.datamodel.GraphLine(
532                       (currentAnnotation.graphMax - currentAnnotation.graphMin) / 2f,
533                       "Threshold", Color.black));
534     }
535
536     if (aboveThreshold != AnnotationColourGradient.NO_THRESHOLD)
537     {
538       adjusting = true;
539       float range = currentAnnotation.graphMax * 1000
540               - currentAnnotation.graphMin * 1000;
541
542       slider.setMinimum((int) (currentAnnotation.graphMin * 1000));
543       slider.setMaximum((int) (currentAnnotation.graphMax * 1000));
544       slider.setValue((int) (currentAnnotation.threshold.value * 1000));
545       thresholdValue.setText(currentAnnotation.threshold.value + "");
546       slider.setMajorTickSpacing((int) (range / 10f));
547       slider.setEnabled(true);
548       thresholdValue.setEnabled(true);
549       adjusting = false;
550     }
551
552     AnnotationColourGradient acg = null;
553     if (currentColours.isSelected())
554     {
555       acg = new AnnotationColourGradient(currentAnnotation,
556               av.getGlobalColourScheme(), aboveThreshold);
557     }
558     else
559     {
560       acg = new AnnotationColourGradient(currentAnnotation,
561               minColour.getBackground(), maxColour.getBackground(),
562               aboveThreshold);
563     }
564     acg.setSeqAssociated(seqAssociated.isSelected());
565
566     if (currentAnnotation.graphMin == 0f
567             && currentAnnotation.graphMax == 0f)
568     {
569       acg.setPredefinedColours(true);
570     }
571
572     acg.thresholdIsMinMax = thresholdIsMin.isSelected();
573
574     av.setGlobalColourScheme(acg);
575
576     if (av.getAlignment().getGroups() != null)
577     {
578
579       for (SequenceGroup sg : ap.av.getAlignment().getGroups())
580       {
581         if (sg.cs == null)
582         {
583           continue;
584         }
585
586         if (currentColours.isSelected())
587         {
588           sg.cs = new AnnotationColourGradient(currentAnnotation, sg.cs,
589                   aboveThreshold);
590           ((AnnotationColourGradient) sg.cs).setSeqAssociated(seqAssociated
591                   .isSelected());
592
593         }
594         else
595         {
596           sg.cs = new AnnotationColourGradient(currentAnnotation,
597                   minColour.getBackground(), maxColour.getBackground(),
598                   aboveThreshold);
599           ((AnnotationColourGradient) sg.cs).setSeqAssociated(seqAssociated
600                   .isSelected());
601         }
602
603       }
604     }
605     ap.alignmentChanged();
606     // ensure all associated views (overviews, structures, etc) are notified of
607     // updated colours.
608     ap.paintAlignment(true);
609   }
610
611   public void ok_actionPerformed(ActionEvent e)
612   {
613     changeColour();
614     try
615     {
616       frame.setClosed(true);
617     } catch (Exception ex)
618     {
619     }
620   }
621
622   public void cancel_actionPerformed(ActionEvent e)
623   {
624     reset();
625     // ensure all original colouring is propagated to listeners.
626     ap.paintAlignment(true);
627     try
628     {
629       frame.setClosed(true);
630     } catch (Exception ex)
631     {
632     }
633   }
634
635   void reset()
636   {
637     av.setGlobalColourScheme(oldcs);
638     if (av.getAlignment().getGroups() != null)
639     {
640
641       for (SequenceGroup sg : ap.av.getAlignment().getGroups())
642       {
643         sg.cs = (ColourSchemeI) oldgroupColours.get(sg);
644       }
645     }
646   }
647
648   public void thresholdCheck_actionPerformed(ActionEvent e)
649   {
650     changeColour();
651   }
652
653   public void annotations_actionPerformed(ActionEvent e)
654   {
655     changeColour();
656   }
657
658   public void threshold_actionPerformed(ActionEvent e)
659   {
660     changeColour();
661   }
662
663   public void thresholdValue_actionPerformed(ActionEvent e)
664   {
665     try
666     {
667       float f = Float.parseFloat(thresholdValue.getText());
668       slider.setValue((int) (f * 1000));
669     } catch (NumberFormatException ex)
670     {
671     }
672   }
673
674   public void valueChanged(boolean updateAllAnnotation)
675   {
676     if (currentColours.isSelected()
677             && !(av.getGlobalColourScheme() instanceof AnnotationColourGradient))
678     {
679       changeColour();
680     }
681     currentAnnotation.threshold.value = (float) slider.getValue() / 1000f;
682     propagateSeqAssociatedThreshold(updateAllAnnotation);
683     ap.paintAlignment(false);
684   }
685
686   private void propagateSeqAssociatedThreshold(boolean allAnnotation)
687   {
688     if (currentAnnotation.sequenceRef == null
689             || currentAnnotation.threshold == null)
690     {
691       return;
692     }
693     // TODO: JAL-1327 only update visible annotation thresholds if allAnnotation
694     // is false, since we only need to provide a quick visual indicator
695
696     float thr = currentAnnotation.threshold.value;
697     for (int i = 0; i < av.getAlignment().getAlignmentAnnotation().length; i++)
698     {
699       AlignmentAnnotation aa = av.getAlignment().getAlignmentAnnotation()[i];
700       if (aa.label.equals(currentAnnotation.label))
701       {
702         aa.threshold.value = thr;
703       }
704     }
705   }
706
707   public void currentColours_actionPerformed(ActionEvent e)
708   {
709     if (currentColours.isSelected())
710     {
711       reset();
712     }
713
714     maxColour.setEnabled(!currentColours.isSelected());
715     minColour.setEnabled(!currentColours.isSelected());
716
717     changeColour();
718   }
719
720   public void thresholdIsMin_actionPerformed(ActionEvent actionEvent)
721   {
722     changeColour();
723   }
724
725 }