JAL-1066 limit available rows to per-sequence when selected and disable if no sequenc...
[jalview.git] / src / jalview / gui / AnnotationColourChooser.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.7)
3  * Copyright (C) 2011 J Procter, AM Waterhouse, J Engelhardt, LM Lui, G Barton, M Clamp, S Searle
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 of the License, or (at your option) any later version.
10  * 
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 package jalview.gui;
19
20 import java.util.*;
21
22 import java.awt.*;
23 import java.awt.event.*;
24
25 import javax.swing.*;
26 import javax.swing.event.*;
27
28 import net.miginfocom.swing.MigLayout;
29
30 import jalview.bin.Cache;
31 import jalview.datamodel.*;
32 import jalview.schemes.*;
33 import java.awt.Dimension;
34
35 public class AnnotationColourChooser extends JPanel
36 {
37   JInternalFrame frame;
38
39   AlignViewport av;
40
41   AlignmentPanel ap;
42
43   ColourSchemeI oldcs;
44
45   Hashtable oldgroupColours;
46
47   jalview.datamodel.AlignmentAnnotation currentAnnotation;
48
49   boolean adjusting = false;
50
51   public AnnotationColourChooser(AlignViewport av, final AlignmentPanel ap)
52   {
53     oldcs = av.getGlobalColourScheme();
54     if (av.getAlignment().getGroups() != null)
55     {
56       oldgroupColours = new Hashtable();
57       for (SequenceGroup sg:ap.av.getAlignment().getGroups())
58       {
59         if (sg.cs != null)
60         {
61           oldgroupColours.put(sg, sg.cs);
62         }
63       }
64     }
65     this.av = av;
66     this.ap = ap;
67     frame = new JInternalFrame();
68     frame.setContentPane(this);
69     frame.setLayer(JLayeredPane.PALETTE_LAYER);
70     Desktop.addInternalFrame(frame, "Colour by Annotation", 520, 215);
71
72     slider.addChangeListener(new ChangeListener()
73     {
74       public void stateChanged(ChangeEvent evt)
75       {
76         if (!adjusting)
77         {
78           thresholdValue.setText(((float) slider.getValue() / 1000f) + "");
79           valueChanged();
80         }
81       }
82     });
83     slider.addMouseListener(new MouseAdapter()
84     {
85       public void mouseReleased(MouseEvent evt)
86       {
87         ap.paintAlignment(true);
88       }
89     });
90
91     if (av.getAlignment().getAlignmentAnnotation() == null)
92     {
93       return;
94     }
95
96     // Always get default shading from preferences.
97     setDefaultMinMax();
98     
99     if (oldcs instanceof AnnotationColourGradient)
100     {
101       AnnotationColourGradient acg = (AnnotationColourGradient) oldcs;
102       currentColours.setSelected(acg.predefinedColours);
103       if (!acg.predefinedColours)
104       {
105         minColour.setBackground(acg.getMinColour());
106         maxColour.setBackground(acg.getMaxColour());
107       }
108       seqAssociated.setSelected(acg.isSeqAssociated());
109     }
110     adjusting = true;
111     annotations = new JComboBox(getAnnotationItems(seqAssociated.isSelected()));
112
113     threshold.addItem("No Threshold");
114     threshold.addItem("Above Threshold");
115     threshold.addItem("Below Threshold");
116
117     if (oldcs instanceof AnnotationColourGradient)
118     {
119       AnnotationColourGradient acg = (AnnotationColourGradient) oldcs;
120       annotations.setSelectedItem(acg.getAnnotation());
121       switch (acg.getAboveThreshold()) {
122       case AnnotationColourGradient.NO_THRESHOLD:
123           threshold.setSelectedItem("No Threshold");
124         break;
125       case AnnotationColourGradient.ABOVE_THRESHOLD:
126           threshold.setSelectedItem("Above Threshold");
127         break;
128       case AnnotationColourGradient.BELOW_THRESHOLD:
129         threshold.setSelectedItem("Below Threshold");
130         break;
131         default:
132           throw new Error("Implementation error: don't know about threshold setting for current AnnotationColourGradient.");
133       }
134       thresholdIsMin.setSelected(acg.thresholdIsMinMax);
135       thresholdValue.setText(""+acg.getAnnotationThreshold());
136     }
137
138     try
139     {
140       jbInit();
141     } catch (Exception ex)
142     {
143     }
144
145     adjusting = false;
146
147     changeColour();
148     frame.invalidate();
149     frame.pack();
150
151   }
152
153   private Vector<String> getAnnotationItems(boolean isSeqAssociated) {
154     Vector<String> list = new Vector<String>();
155     int index = 1;
156     int[] anmap = new int[av.getAlignment().getAlignmentAnnotation().length];
157     boolean enableSeqAss=false;
158     for (int i = 0; i < av.getAlignment().getAlignmentAnnotation().length; i++)
159     {
160       if (av.getAlignment().getAlignmentAnnotation()[i].sequenceRef==null)
161       {
162         if (isSeqAssociated)
163           {
164           continue;
165           }
166       } else {
167         enableSeqAss=true;
168       }
169       String label = av.getAlignment().getAlignmentAnnotation()[i].label;
170       if (!list.contains(label))
171         {anmap[list.size()] = i;
172         list.addElement(label);
173         
174         }else {
175         if (!isSeqAssociated) {
176           anmap[list.size()] = i;
177           list.addElement(label + "_" + (index++));
178         }
179       }
180     }
181     seqAssociated.setEnabled(enableSeqAss);
182     annmap = new int[list.size()];
183     System.arraycopy(anmap, 0,  annmap,  0,  annmap.length);
184     return list;
185   }
186   private void setDefaultMinMax()
187   {
188     minColour.setBackground(Cache.getDefaultColour("ANNOTATIONCOLOUR_MIN", Color.orange));
189     maxColour.setBackground(Cache.getDefaultColour("ANNOTATIONCOLOUR_MAX", Color.red));
190   }
191
192   public AnnotationColourChooser()
193   {
194     try
195     {
196       jbInit();
197     } catch (Exception ex)
198     {
199       ex.printStackTrace();
200     }
201   }
202
203   private void jbInit() throws Exception
204   {
205     minColour.setFont(JvSwingUtils.getLabelFont());
206     minColour.setBorder(BorderFactory.createEtchedBorder());
207     minColour.setPreferredSize(new Dimension(40, 20));
208     minColour.setToolTipText("Minimum Colour");
209     minColour.addMouseListener(new MouseAdapter()
210     {
211       public void mousePressed(MouseEvent e)
212       {
213         if (minColour.isEnabled())
214         {
215           minColour_actionPerformed();
216         }
217       }
218     });
219     maxColour.setFont(JvSwingUtils.getLabelFont());
220     maxColour.setBorder(BorderFactory.createEtchedBorder());
221     maxColour.setPreferredSize(new Dimension(40, 20));
222     maxColour.setToolTipText("Maximum Colour");
223     maxColour.addMouseListener(new MouseAdapter()
224     {
225       public void mousePressed(MouseEvent e)
226       {
227         if (maxColour.isEnabled())
228         {
229           maxColour_actionPerformed();
230         }
231       }
232     });
233     ok.setOpaque(false);
234     ok.setText("OK");
235     ok.addActionListener(new ActionListener()
236     {
237       public void actionPerformed(ActionEvent e)
238       {
239         ok_actionPerformed(e);
240       }
241     });
242     cancel.setOpaque(false);
243     cancel.setText("Cancel");
244     cancel.addActionListener(new ActionListener()
245     {
246       public void actionPerformed(ActionEvent e)
247       {
248         cancel_actionPerformed(e);
249       }
250     });
251     defColours.setOpaque(false);
252     defColours.setText("Defaults");
253     defColours.setToolTipText("Reset min and max colours to defaults from user preferences.");
254     defColours.addActionListener(new ActionListener()
255     {
256       
257       @Override
258       public void actionPerformed(ActionEvent arg0)
259       {
260         resetColours_actionPerformed(arg0);
261       }
262     });
263     
264     annotations.addActionListener(new ActionListener()
265     {
266       public void actionPerformed(ActionEvent e)
267       {
268         annotations_actionPerformed(e);
269       }
270     });
271     threshold.addActionListener(new ActionListener()
272     {
273       public void actionPerformed(ActionEvent e)
274       {
275         threshold_actionPerformed(e);
276       }
277     });
278     thresholdValue.addActionListener(new ActionListener()
279     {
280       public void actionPerformed(ActionEvent e)
281       {
282         thresholdValue_actionPerformed(e);
283       }
284     });
285     slider.setPaintLabels(false);
286     slider.setPaintTicks(true);
287     slider.setBackground(Color.white);
288     slider.setEnabled(false);
289     slider.setOpaque(false);
290     slider.setPreferredSize(new Dimension(100, 32));
291     thresholdValue.setEnabled(false);
292     thresholdValue.setColumns(7);
293     currentColours.setFont(JvSwingUtils.getLabelFont());
294     currentColours.setOpaque(false);
295     currentColours.setText("Use Original Colours");
296     currentColours.addActionListener(new ActionListener()
297     {
298       public void actionPerformed(ActionEvent e)
299       {
300         currentColours_actionPerformed(e);
301       }
302     });
303     thresholdIsMin.setBackground(Color.white);
304     thresholdIsMin.setFont(JvSwingUtils.getLabelFont());
305     thresholdIsMin.setText("Threshold is Min/Max");
306     thresholdIsMin.addActionListener(new ActionListener()
307     {
308       public void actionPerformed(ActionEvent actionEvent)
309       {
310         thresholdIsMin_actionPerformed(actionEvent);
311       }
312     });
313     seqAssociated.setBackground(Color.white);
314     seqAssociated.setFont(JvSwingUtils.getLabelFont());
315     seqAssociated.setText("Per-sequence only");
316     seqAssociated.addActionListener(new ActionListener()
317     {
318       
319       @Override
320       public void actionPerformed(ActionEvent arg0)
321       {
322         seqAssociated_actionPerformed(arg0);
323       }
324     });
325     
326     this.setLayout(borderLayout1);
327     jPanel2.setLayout(new MigLayout("","[left][center][right]","[][][]"));
328     jPanel1.setBackground(Color.white);
329     jPanel2.setBackground(Color.white);
330
331     jPanel1.add(ok);
332     jPanel1.add(cancel);
333     jPanel2.add(annotations, "grow, wrap");
334     jPanel2.add(seqAssociated);
335     jPanel2.add(currentColours);
336     JPanel colpanel = new JPanel(new FlowLayout());
337     colpanel.setBackground(Color.white);
338     colpanel.add(minColour);
339     colpanel.add(maxColour);
340     jPanel2.add(colpanel, "wrap");
341     jPanel2.add(threshold);
342     jPanel2.add(defColours,"skip 1, wrap");
343     jPanel2.add(thresholdIsMin);
344     jPanel2.add(slider, "grow");
345     jPanel2.add(thresholdValue, "grow");
346     this.add(jPanel1, java.awt.BorderLayout.SOUTH);
347     this.add(jPanel2, java.awt.BorderLayout.CENTER);
348     this.validate();
349   }
350
351   protected void seqAssociated_actionPerformed(ActionEvent arg0)
352   {
353     adjusting=true;
354     String cursel = (String) annotations.getSelectedItem();
355     boolean isvalid=false,isseqs=seqAssociated.isSelected();
356     this.annotations.removeAllItems();
357     for (String anitem:getAnnotationItems(seqAssociated.isSelected()))
358     {
359       if (anitem.equals(            cursel) || (isseqs && cursel.startsWith(anitem)))
360       {
361         isvalid=true;
362         cursel=anitem;
363       }
364       this.annotations.addItem(anitem);
365     }
366     adjusting=false;
367     if (isvalid) { this.annotations.setSelectedItem(cursel); } else {
368       if (annotations.getItemCount()>0) {
369         annotations.setSelectedIndex(0);
370       }
371     }
372   }
373
374   protected void resetColours_actionPerformed(ActionEvent arg0)
375   {
376     setDefaultMinMax();
377     changeColour();
378   }
379
380   JComboBox annotations;
381   int[] annmap;
382   JPanel minColour = new JPanel();
383
384   JPanel maxColour = new JPanel();
385   JButton defColours = new JButton();
386   JButton ok = new JButton();
387
388   JButton cancel = new JButton();
389
390   JPanel jPanel1 = new JPanel();
391   JPanel jPanel2 = new JPanel();
392   
393   BorderLayout borderLayout1 = new BorderLayout();
394
395   JComboBox threshold = new JComboBox();
396
397
398   JSlider slider = new JSlider();
399
400   JTextField thresholdValue = new JTextField(20);
401
402   JCheckBox currentColours = new JCheckBox();
403
404   JCheckBox thresholdIsMin = new JCheckBox();
405   
406   JCheckBox seqAssociated = new JCheckBox();
407
408   public void minColour_actionPerformed()
409   {
410     Color col = JColorChooser.showDialog(this,
411             "Select Colour for Minimum Value", minColour.getBackground());
412     if (col != null)
413     {
414       minColour.setBackground(col);
415     }
416     minColour.repaint();
417     changeColour();
418   }
419
420   public void maxColour_actionPerformed()
421   {
422     Color col = JColorChooser.showDialog(this,
423             "Select Colour for Maximum Value", maxColour.getBackground());
424     if (col != null)
425     {
426       maxColour.setBackground(col);
427     }
428     maxColour.repaint();
429     changeColour();
430   }
431
432   void changeColour()
433   {
434     // Check if combobox is still adjusting
435     if (adjusting)
436     {
437       return;
438     }
439
440     currentAnnotation = av.getAlignment().getAlignmentAnnotation()[annmap[annotations
441             .getSelectedIndex()]];
442
443     int aboveThreshold = -1;
444     if (threshold.getSelectedItem().equals("Above Threshold"))
445     {
446       aboveThreshold = AnnotationColourGradient.ABOVE_THRESHOLD;
447     }
448     else if (threshold.getSelectedItem().equals("Below Threshold"))
449     {
450       aboveThreshold = AnnotationColourGradient.BELOW_THRESHOLD;
451     }
452
453     slider.setEnabled(true);
454     thresholdValue.setEnabled(true);
455     thresholdIsMin.setEnabled(true);
456
457     if (aboveThreshold == AnnotationColourGradient.NO_THRESHOLD)
458     {
459       slider.setEnabled(false);
460       thresholdValue.setEnabled(false);
461       thresholdValue.setText("");
462       thresholdIsMin.setEnabled(false);
463     }
464     else if (aboveThreshold != AnnotationColourGradient.NO_THRESHOLD
465             && currentAnnotation.threshold == null)
466     {
467       currentAnnotation
468               .setThreshold(new jalview.datamodel.GraphLine(
469                       (currentAnnotation.graphMax - currentAnnotation.graphMin) / 2f,
470                       "Threshold", Color.black));
471     }
472
473     if (aboveThreshold != AnnotationColourGradient.NO_THRESHOLD)
474     {
475       adjusting = true;
476       float range = currentAnnotation.graphMax * 1000
477               - currentAnnotation.graphMin * 1000;
478
479       slider.setMinimum((int) (currentAnnotation.graphMin * 1000));
480       slider.setMaximum((int) (currentAnnotation.graphMax * 1000));
481       slider.setValue((int) (currentAnnotation.threshold.value * 1000));
482       thresholdValue.setText(currentAnnotation.threshold.value + "");
483       slider.setMajorTickSpacing((int) (range / 10f));
484       slider.setEnabled(true);
485       thresholdValue.setEnabled(true);
486       adjusting = false;
487     }
488
489     AnnotationColourGradient acg = null;
490     if (currentColours.isSelected())
491     {
492       acg = new AnnotationColourGradient(currentAnnotation,
493               av.getGlobalColourScheme(), aboveThreshold);
494     }
495     else
496     {
497       acg = new AnnotationColourGradient(currentAnnotation,
498               minColour.getBackground(), maxColour.getBackground(),
499               aboveThreshold);
500     }
501     acg.setSeqAssociated(seqAssociated.isSelected());
502
503     if (currentAnnotation.graphMin == 0f
504             && currentAnnotation.graphMax == 0f)
505     {
506       acg.predefinedColours = true;
507     }
508
509     acg.thresholdIsMinMax = thresholdIsMin.isSelected();
510
511     av.setGlobalColourScheme(acg);
512
513     if (av.getAlignment().getGroups() != null)
514     {
515       
516       for (SequenceGroup sg:ap.av.getAlignment().getGroups())
517       {
518         if (sg.cs == null)
519         {
520           continue;
521         }
522
523         if (currentColours.isSelected())
524         {
525           sg.cs = new AnnotationColourGradient(currentAnnotation, sg.cs,
526                   aboveThreshold);
527           ((AnnotationColourGradient)sg.cs).setSeqAssociated(seqAssociated.isSelected());
528
529         }
530         else
531         {
532           sg.cs = new AnnotationColourGradient(currentAnnotation,
533                   minColour.getBackground(), maxColour.getBackground(),
534                   aboveThreshold);
535           ((AnnotationColourGradient)sg.cs).setSeqAssociated(seqAssociated.isSelected());
536         }
537
538       }
539     }
540     ap.alignmentChanged();
541     // ensure all associated views (overviews, structures, etc) are notified of updated colours.
542     ap.paintAlignment(true);
543   }
544
545   public void ok_actionPerformed(ActionEvent e)
546   {
547     changeColour();
548     try
549     {
550       frame.setClosed(true);
551     } catch (Exception ex)
552     {
553     }
554   }
555
556   public void cancel_actionPerformed(ActionEvent e)
557   {
558     reset();
559     // ensure all original colouring is propagated to listeners. 
560     ap.paintAlignment(true);
561     try
562     {
563       frame.setClosed(true);
564     } catch (Exception ex)
565     {
566     }
567   }
568
569   void reset()
570   {
571     av.setGlobalColourScheme(oldcs);
572     if (av.getAlignment().getGroups() != null)
573     {
574       
575       for (SequenceGroup sg:ap.av.getAlignment().getGroups())
576       {
577         sg.cs = (ColourSchemeI) oldgroupColours.get(sg);
578       }
579     }
580   }
581
582   public void thresholdCheck_actionPerformed(ActionEvent e)
583   {
584     changeColour();
585   }
586
587   public void annotations_actionPerformed(ActionEvent e)
588   {
589     changeColour();
590   }
591
592   public void threshold_actionPerformed(ActionEvent e)
593   {
594     changeColour();
595   }
596
597   public void thresholdValue_actionPerformed(ActionEvent e)
598   {
599     try
600     {
601       float f = Float.parseFloat(thresholdValue.getText());
602       slider.setValue((int) (f * 1000));
603     } catch (NumberFormatException ex)
604     {
605     }
606   }
607
608   public void valueChanged()
609   {
610     if (currentColours.isSelected()
611             && !(av.getGlobalColourScheme() instanceof AnnotationColourGradient))
612     {
613       changeColour();
614     }
615
616     currentAnnotation.threshold.value = (float) slider.getValue() / 1000f;
617     ap.paintAlignment(false);
618   }
619
620   public void currentColours_actionPerformed(ActionEvent e)
621   {
622     if (currentColours.isSelected())
623     {
624       reset();
625     }
626
627     maxColour.setEnabled(!currentColours.isSelected());
628     minColour.setEnabled(!currentColours.isSelected());
629
630     changeColour();
631   }
632
633   public void thresholdIsMin_actionPerformed(ActionEvent actionEvent)
634   {
635     changeColour();
636   }
637
638 }