Merge branch 'develop' into bug/JAL-2399textColour
[jalview.git] / src / jalview / appletgui / AnnotationColourChooser.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ 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.appletgui;
22
23 import jalview.datamodel.AlignmentAnnotation;
24 import jalview.datamodel.SequenceGroup;
25 import jalview.schemes.AnnotationColourGradient;
26 import jalview.schemes.ColourSchemeI;
27 import jalview.util.MessageManager;
28
29 import java.awt.BorderLayout;
30 import java.awt.Button;
31 import java.awt.Checkbox;
32 import java.awt.Choice;
33 import java.awt.Color;
34 import java.awt.Dimension;
35 import java.awt.FlowLayout;
36 import java.awt.Font;
37 import java.awt.Frame;
38 import java.awt.Panel;
39 import java.awt.Scrollbar;
40 import java.awt.TextField;
41 import java.awt.event.ActionEvent;
42 import java.awt.event.ActionListener;
43 import java.awt.event.AdjustmentEvent;
44 import java.awt.event.AdjustmentListener;
45 import java.awt.event.ItemEvent;
46 import java.awt.event.ItemListener;
47 import java.awt.event.MouseEvent;
48 import java.awt.event.MouseListener;
49 import java.util.HashMap;
50 import java.util.Map;
51 import java.util.Vector;
52
53 public class AnnotationColourChooser extends Panel implements
54         ActionListener, AdjustmentListener, ItemListener, MouseListener
55 {
56   Frame frame;
57
58   AlignViewport av;
59
60   AlignmentPanel ap;
61
62   ColourSchemeI oldcs;
63
64   Map<SequenceGroup, ColourSchemeI> oldgroupColours;
65
66   /*
67    * map from annotation to its menu item display label
68    * - so we know which item to pre-select on restore
69    */
70   private Map<AlignmentAnnotation, String> annotationLabels;
71
72   AlignmentAnnotation currentAnnotation;
73
74   boolean adjusting = false;
75
76   public AnnotationColourChooser(AlignViewport av, AlignmentPanel ap)
77   {
78     try
79     {
80       jbInit();
81     } catch (Exception ex)
82     {
83     }
84
85     oldcs = av.getGlobalColourScheme();
86     if (av.getAlignment().getGroups() != null)
87     {
88       oldgroupColours = new HashMap<SequenceGroup, ColourSchemeI>();
89       for (SequenceGroup sg : ap.av.getAlignment().getGroups())
90       {
91         oldgroupColours.put(sg, sg.getColourScheme());
92       }
93     }
94     this.av = av;
95     this.ap = ap;
96
97     slider.addAdjustmentListener(this);
98     slider.addMouseListener(this);
99
100     AlignmentAnnotation[] anns = av.getAlignment().getAlignmentAnnotation();
101     if (anns == null)
102     {
103       return;
104     }
105
106     setDefaultMinMax();
107
108     adjusting = true;
109     if (oldcs instanceof AnnotationColourGradient)
110     {
111       AnnotationColourGradient acg = (AnnotationColourGradient) oldcs;
112       currentColours.setState(acg.isPredefinedColours()
113               || acg.getBaseColour() != null);
114       if (!acg.isPredefinedColours() && acg.getBaseColour() == null)
115       {
116         minColour.setBackground(acg.getMinColour());
117         maxColour.setBackground(acg.getMaxColour());
118       }
119       // seqAssociated.setState(acg.isSeqAssociated());
120     }
121
122     Vector<String> list = getAnnotationItems();
123
124     for (int i = 0; i < list.size(); i++)
125     {
126       annotations.addItem(list.elementAt(i).toString());
127     }
128
129     threshold.addItem(MessageManager
130             .getString("label.threshold_feature_no_threshold"));
131     threshold.addItem(MessageManager
132             .getString("label.threshold_feature_above_threshold"));
133     threshold.addItem(MessageManager
134             .getString("label.threshold_feature_below_threshold"));
135
136     if (oldcs instanceof AnnotationColourGradient)
137     {
138       AnnotationColourGradient acg = (AnnotationColourGradient) oldcs;
139       String label = annotationLabels.get(acg.getAnnotation());
140       annotations.select(label);
141       switch (acg.getAboveThreshold())
142       {
143       case AnnotationColourGradient.NO_THRESHOLD:
144         threshold.select(0);
145         break;
146       case AnnotationColourGradient.ABOVE_THRESHOLD:
147         threshold.select(1);
148         break;
149       case AnnotationColourGradient.BELOW_THRESHOLD:
150         threshold.select(1);
151         break;
152       default:
153         throw new Error(
154                 MessageManager
155                         .getString("error.implementation_error_dont_know_threshold_annotationcolourgradient"));
156       }
157       thresholdIsMin.setState(acg.isThresholdIsMinMax());
158       thresholdValue.setText("" + acg.getAnnotationThreshold());
159     }
160
161     adjusting = false;
162
163     changeColour();
164
165     frame = new Frame();
166     frame.add(this);
167     jalview.bin.JalviewLite.addFrame(frame,
168             MessageManager.getString("label.colour_by_annotation"), 560,
169             175);
170     validate();
171   }
172
173   /**
174    * Builds and returns a list of menu items (display text) for choice of
175    * annotation. Also builds a map between annotations and their display labels.
176    * 
177    * @return
178    */
179   protected Vector<String> getAnnotationItems()
180   {
181     // TODO remove duplication with gui.AnnotationRowFilter
182     // TODO add 'per sequence only' option / parameter
183
184     annotationLabels = new HashMap<AlignmentAnnotation, String>();
185     Vector<String> list = new Vector<String>();
186     AlignmentAnnotation[] anns = av.getAlignment().getAlignmentAnnotation();
187     if (anns == null)
188     {
189       return list;
190     }
191     int index = 1;
192     for (int i = 0; i < anns.length; i++)
193     {
194       String label = anns[i].label;
195       if (anns[i].sequenceRef != null)
196       {
197         /*
198          * be helpful and include sequence id in label for
199          * sequence-associated annotation (JAL-2236)
200          */
201         label = label + "_" + anns[i].sequenceRef.getName();
202       }
203       if (!list.contains(label))
204       {
205         list.addElement(label);
206         annotationLabels.put(anns[i], label);
207       }
208       else
209       {
210         label = label + "_" + (index++);
211         list.addElement(label);
212         annotationLabels.put(anns[i], label);
213       }
214     }
215     return list;
216   }
217
218   private void setDefaultMinMax()
219   {
220     minColour.setBackground(av.applet.getDefaultColourParameter(
221             "ANNOTATIONCOLOUR_MIN", Color.orange));
222     maxColour.setBackground(av.applet.getDefaultColourParameter(
223             "ANNOTATIONCOLOUR_MAX", Color.red));
224
225   }
226
227   public AnnotationColourChooser()
228   {
229     try
230     {
231       jbInit();
232     } catch (Exception ex)
233     {
234       ex.printStackTrace();
235     }
236   }
237
238   private void jbInit() throws Exception
239   {
240     minColour.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
241     minColour.setLabel(MessageManager.getString("label.min_colour"));
242     minColour.addActionListener(this);
243
244     maxColour.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
245     maxColour.setLabel(MessageManager.getString("label.max_colour"));
246     maxColour.addActionListener(this);
247
248     thresholdIsMin.addItemListener(this);
249     ok.setLabel(MessageManager.getString("action.ok"));
250     ok.addActionListener(this);
251
252     cancel.setLabel(MessageManager.getString("action.cancel"));
253     cancel.addActionListener(this);
254
255     defColours.setLabel(MessageManager.getString("action.set_defaults"));
256     defColours.addActionListener(this);
257
258     annotations.addItemListener(this);
259
260     thresholdValue.addActionListener(this);
261     slider.setBackground(Color.white);
262     slider.setPreferredSize(new Dimension(193, 21));
263     slider.setEnabled(false);
264     thresholdValue.setPreferredSize(new Dimension(79, 22));
265     thresholdValue.setEnabled(false);
266     thresholdValue.setColumns(5);
267     currentColours.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
268     currentColours.setLabel(MessageManager
269             .getString("label.use_original_colours"));
270     currentColours.addItemListener(this);
271
272     thresholdIsMin.setBackground(Color.white);
273     thresholdIsMin.setLabel(MessageManager
274             .getString("label.threshold_minmax"));
275
276     this.setLayout(borderLayout1);
277
278     jPanel1.setBackground(Color.white);
279
280     jPanel2.setLayout(new FlowLayout());
281     jPanel2.setBackground(Color.white);
282     threshold.addItemListener(this);
283     jPanel3.setLayout(new FlowLayout());
284     jPanel3.setBackground(Color.white);
285     Panel jPanel4 = new Panel();
286     jPanel4.setLayout(new BorderLayout());
287     jPanel4.setBackground(Color.white);
288
289     jPanel1.add(ok);
290     jPanel1.add(cancel);
291
292     jPanel2.add(annotations);
293     jPanel2.add(currentColours);
294     jPanel2.add(minColour);
295     jPanel2.add(maxColour);
296
297     jPanel4.add(thresholdIsMin, BorderLayout.WEST);
298     jPanel4.add(slider, BorderLayout.CENTER);
299     jPanel4.add(thresholdValue, BorderLayout.EAST);
300
301     Panel jPanel34 = new Panel();
302     jPanel34.setLayout(new BorderLayout());
303     jPanel34.setBackground(Color.white);
304     jPanel34.add(jPanel2, BorderLayout.NORTH);
305     jPanel34.add(threshold, BorderLayout.WEST);
306     jPanel3.add(defColours);
307     jPanel34.add(jPanel3, BorderLayout.EAST);
308     jPanel34.add(jPanel4, BorderLayout.SOUTH);
309
310     this.add(jPanel34, java.awt.BorderLayout.CENTER);
311     this.add(jPanel1, java.awt.BorderLayout.SOUTH);
312
313   }
314
315   Choice annotations = new Choice();
316
317   Button minColour = new Button();
318
319   Button maxColour = new Button();
320
321   Button ok = new Button();
322
323   Button cancel = new Button();
324
325   Button defColours = new Button();
326
327   Panel jPanel1 = new Panel();
328
329   Panel jPanel2 = new Panel();
330
331   Choice threshold = new Choice();
332
333   FlowLayout flowLayout1 = new FlowLayout();
334
335   Panel jPanel3 = new Panel();
336
337   Scrollbar slider = new Scrollbar(Scrollbar.HORIZONTAL);
338
339   TextField thresholdValue = new TextField(20);
340
341   Checkbox currentColours = new Checkbox();
342
343   BorderLayout borderLayout1 = new BorderLayout();
344
345   Checkbox thresholdIsMin = new Checkbox();
346
347   @Override
348   public void actionPerformed(ActionEvent evt)
349   {
350     if (evt.getSource() == thresholdValue)
351     {
352       try
353       {
354         float f = new Float(thresholdValue.getText()).floatValue();
355         slider.setValue((int) (f * 1000));
356         adjustmentValueChanged(null);
357       } catch (NumberFormatException ex)
358       {
359       }
360     }
361     else if (evt.getSource() == minColour)
362     {
363       minColour_actionPerformed(null);
364     }
365     else if (evt.getSource() == maxColour)
366     {
367       maxColour_actionPerformed(null);
368     }
369     else if (evt.getSource() == defColours)
370     {
371       defColour_actionPerformed();
372     }
373     else if (evt.getSource() == ok)
374     {
375       frame.setVisible(false);
376     }
377     else if (evt.getSource() == cancel)
378     {
379       reset();
380       ap.paintAlignment(true);
381       frame.setVisible(false);
382     }
383
384     else
385     {
386       changeColour();
387     }
388   }
389
390   @Override
391   public void itemStateChanged(ItemEvent evt)
392   {
393     if (evt.getSource() == currentColours)
394     {
395       if (currentColours.getState())
396       {
397         reset();
398       }
399
400       maxColour.setEnabled(!currentColours.getState());
401       minColour.setEnabled(!currentColours.getState());
402
403     }
404
405     changeColour();
406   }
407
408   @Override
409   public void adjustmentValueChanged(AdjustmentEvent evt)
410   {
411     if (!adjusting)
412     {
413       thresholdValue.setText((slider.getValue() / 1000f) + "");
414       if (currentColours.getState()
415               && !(av.getGlobalColourScheme() instanceof AnnotationColourGradient))
416       {
417         changeColour();
418       }
419
420       currentAnnotation.threshold.value = slider.getValue() / 1000f;
421       ap.paintAlignment(false);
422     }
423   }
424
425   public void minColour_actionPerformed(Color newCol)
426   {
427     if (newCol != null)
428     {
429       minColour.setBackground(newCol);
430       minColour.repaint();
431       changeColour();
432     }
433     else
434     {
435       new UserDefinedColours(this, "Min Colour", minColour.getBackground());
436     }
437
438   }
439
440   public void maxColour_actionPerformed(Color newCol)
441   {
442     if (newCol != null)
443     {
444       maxColour.setBackground(newCol);
445       maxColour.repaint();
446       changeColour();
447     }
448     else
449     {
450       new UserDefinedColours(this, "Max Colour", maxColour.getBackground());
451     }
452   }
453
454   public void defColour_actionPerformed()
455   {
456     setDefaultMinMax();
457     minColour.repaint();
458     maxColour.repaint();
459     changeColour();
460   }
461
462   void changeColour()
463   {
464     // Check if combobox is still adjusting
465     if (adjusting)
466     {
467       return;
468     }
469
470     currentAnnotation = av.getAlignment().getAlignmentAnnotation()[annotations
471             .getSelectedIndex()];
472
473     int aboveThreshold = -1;
474     if (threshold.getSelectedIndex() == 1)
475     {
476       aboveThreshold = AnnotationColourGradient.ABOVE_THRESHOLD;
477     }
478     else if (threshold.getSelectedIndex() == 2)
479     {
480       aboveThreshold = AnnotationColourGradient.BELOW_THRESHOLD;
481     }
482
483     slider.setEnabled(true);
484     thresholdValue.setEnabled(true);
485     thresholdIsMin.setEnabled(true);
486
487     if (aboveThreshold == AnnotationColourGradient.NO_THRESHOLD)
488     {
489       slider.setEnabled(false);
490       thresholdValue.setEnabled(false);
491       thresholdIsMin.setEnabled(false);
492       thresholdValue.setText("");
493     }
494     else if (aboveThreshold != AnnotationColourGradient.NO_THRESHOLD
495             && currentAnnotation.threshold == null)
496     {
497       currentAnnotation
498               .setThreshold(new jalview.datamodel.GraphLine(
499                       (currentAnnotation.graphMax - currentAnnotation.graphMin) / 2f,
500                       "Threshold", Color.black));
501     }
502
503     if (aboveThreshold != AnnotationColourGradient.NO_THRESHOLD)
504     {
505       adjusting = true;
506
507       slider.setMinimum((int) (currentAnnotation.graphMin * 1000));
508       slider.setMaximum((int) (currentAnnotation.graphMax * 1000));
509       slider.setValue((int) (currentAnnotation.threshold.value * 1000));
510       thresholdValue.setText(currentAnnotation.threshold.value + "");
511       slider.setEnabled(true);
512       thresholdValue.setEnabled(true);
513       adjusting = false;
514     }
515
516     AnnotationColourGradient acg = null;
517     if (currentColours.getState())
518     {
519     }
520     else
521     {
522       acg = new AnnotationColourGradient(currentAnnotation,
523               minColour.getBackground(), maxColour.getBackground(),
524               aboveThreshold);
525     }
526
527     if (currentAnnotation.graphMin == 0f
528             && currentAnnotation.graphMax == 0f)
529     {
530       acg.setPredefinedColours(true);
531     }
532
533     acg.setThresholdIsMinMax(thresholdIsMin.getState());
534
535     av.setGlobalColourScheme(acg);
536
537     // TODO: per group colour propagation not always desired
538     if (av.getAlignment().getGroups() != null)
539     {
540       for (SequenceGroup sg : ap.av.getAlignment().getGroups())
541       {
542         if (sg.getColourScheme() == null)
543         {
544           continue;
545         }
546
547         if (currentColours.getState())
548         {
549           sg.setColourScheme(new AnnotationColourGradient(
550                   currentAnnotation, sg.getColourScheme(), aboveThreshold));
551         }
552         else
553         {
554           sg.setColourScheme(new AnnotationColourGradient(
555                   currentAnnotation, minColour.getBackground(), maxColour
556                           .getBackground(), aboveThreshold));
557         }
558       }
559     }
560
561     // update colours in linked windows
562     ap.alignmentChanged();
563     ap.paintAlignment(true);
564   }
565
566   void reset()
567   {
568     av.setGlobalColourScheme(oldcs);
569     if (av.getAlignment().getGroups() != null)
570     {
571       for (SequenceGroup sg : ap.av.getAlignment().getGroups())
572       {
573         sg.setColourScheme(oldgroupColours.get(sg));
574       }
575     }
576     ap.paintAlignment(true);
577   }
578
579   @Override
580   public void mouseClicked(MouseEvent evt)
581   {
582   }
583
584   @Override
585   public void mousePressed(MouseEvent evt)
586   {
587   }
588
589   @Override
590   public void mouseReleased(MouseEvent evt)
591   {
592     ap.paintAlignment(true);
593   }
594
595   @Override
596   public void mouseEntered(MouseEvent evt)
597   {
598   }
599
600   @Override
601   public void mouseExited(MouseEvent evt)
602   {
603   }
604
605 }