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