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