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