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