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