JAL-2738 copy to spikes/mungo
[jalview.git] / src / jalview / gui / AnnotationRowFilter.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.gui;
22
23 import jalview.datamodel.AlignmentAnnotation;
24 import jalview.datamodel.GraphLine;
25 import jalview.schemes.AnnotationColourGradient;
26 import jalview.util.MessageManager;
27
28 import java.awt.Color;
29 import java.awt.Dimension;
30 import java.awt.event.ActionEvent;
31 import java.awt.event.ActionListener;
32 import java.awt.event.FocusAdapter;
33 import java.awt.event.FocusEvent;
34 import java.awt.event.ItemEvent;
35 import java.awt.event.ItemListener;
36 import java.awt.event.MouseAdapter;
37 import java.awt.event.MouseEvent;
38 import java.util.HashMap;
39 import java.util.Map;
40 import java.util.Vector;
41
42 import javax.swing.JButton;
43 import javax.swing.JCheckBox;
44 import javax.swing.JComboBox;
45 import javax.swing.JInternalFrame;
46 import javax.swing.JPanel;
47 import javax.swing.JSlider;
48 import javax.swing.JTextField;
49 import javax.swing.event.ChangeEvent;
50 import javax.swing.event.ChangeListener;
51
52 @SuppressWarnings("serial")
53 public abstract class AnnotationRowFilter extends JPanel
54 {
55   protected AlignViewport av;
56
57   protected AlignmentPanel ap;
58
59   protected int[] annmap;
60
61   protected boolean adjusting = false;
62
63   protected JCheckBox seqAssociated = new JCheckBox();
64
65   protected JCheckBox percentThreshold = new JCheckBox();
66
67   protected JSlider slider = new JSlider();
68
69   protected JTextField thresholdValue = new JTextField(20);
70
71   protected JInternalFrame frame;
72
73   protected JButton ok = new JButton();
74
75   protected JButton cancel = new JButton();
76
77   /**
78    * enabled if the user is dragging the slider - try to keep updates to a
79    * minimun
80    */
81   protected boolean sliderDragging = false;
82
83   protected JComboBox<String> threshold = new JComboBox<String>();
84
85   protected JComboBox<String> annotations;
86
87   /*
88    * map from annotation to its menu item display label
89    * - so we know which item to pre-select on restore
90    */
91   private Map<AlignmentAnnotation, String> annotationLabels;
92
93   private AlignmentAnnotation currentAnnotation;
94
95   /**
96    * Constructor
97    * 
98    * @param viewport
99    * @param alignPanel
100    */
101   public AnnotationRowFilter(AlignViewport viewport,
102           final AlignmentPanel alignPanel)
103   {
104     this.av = viewport;
105     this.ap = alignPanel;
106     thresholdValue.addFocusListener(new FocusAdapter()
107     {
108       @Override
109       public void focusLost(FocusEvent e)
110       {
111         thresholdValue_actionPerformed();
112       }
113     });
114   }
115
116   protected void addSliderChangeListener()
117   {
118
119     slider.addChangeListener(new ChangeListener()
120     {
121       @Override
122       public void stateChanged(ChangeEvent evt)
123       {
124         if (!adjusting)
125         {
126           setThresholdValueText();
127           valueChanged(!sliderDragging);
128         }
129       }
130     });
131   }
132
133   /**
134    * update the text field from the threshold slider. preserves state of
135    * 'adjusting' so safe to call in init.
136    */
137   protected void setThresholdValueText()
138   {
139     boolean oldadj = adjusting;
140     adjusting = true;
141     if (percentThreshold.isSelected())
142     {
143       thresholdValue.setText("" + (slider.getValue() - slider.getMinimum())
144               * 100f / (slider.getMaximum() - slider.getMinimum()));
145     }
146     else
147     {
148       thresholdValue.setText((slider.getValue() / 1000f) + "");
149     }
150     adjusting = oldadj;
151   }
152
153   protected void addSliderMouseListeners()
154   {
155
156     slider.addMouseListener(new MouseAdapter()
157     {
158       @Override
159       public void mousePressed(MouseEvent e)
160       {
161         sliderDragging = true;
162         super.mousePressed(e);
163       }
164
165       @Override
166       public void mouseDragged(MouseEvent e)
167       {
168         sliderDragging = true;
169         super.mouseDragged(e);
170       }
171
172       @Override
173       public void mouseReleased(MouseEvent evt)
174       {
175         if (sliderDragging)
176         {
177           sliderDragging = false;
178           valueChanged(true);
179         }
180         ap.paintAlignment(true);
181       }
182     });
183   }
184
185   /**
186    * Builds and returns a list of menu items (display text) for choice of
187    * annotation. Also builds maps between annotations, their positions in the
188    * list, and their display labels in the list.
189    * 
190    * @param isSeqAssociated
191    * @return
192    */
193   public Vector<String> getAnnotationItems(boolean isSeqAssociated)
194   {
195     annotationLabels = new HashMap<AlignmentAnnotation, String>();
196
197     Vector<String> list = new Vector<String>();
198     int index = 1;
199     int[] anmap = new int[av.getAlignment()
200             .getAlignmentAnnotation().length];
201     seqAssociated.setEnabled(false);
202     for (int i = 0; i < av.getAlignment()
203             .getAlignmentAnnotation().length; i++)
204     {
205       AlignmentAnnotation annotation = av.getAlignment()
206               .getAlignmentAnnotation()[i];
207       if (annotation.sequenceRef == null)
208       {
209         if (isSeqAssociated)
210         {
211           continue;
212         }
213       }
214       else
215       {
216         seqAssociated.setEnabled(true);
217       }
218       String label = annotation.label;
219       // add associated sequence ID if available
220       if (!isSeqAssociated && annotation.sequenceRef != null)
221       {
222         label = label + "_" + annotation.sequenceRef.getName();
223       }
224       // make label unique
225       if (!list.contains(label))
226       {
227         anmap[list.size()] = i;
228         list.add(label);
229         annotationLabels.put(annotation, label);
230       }
231       else
232       {
233         if (!isSeqAssociated)
234         {
235           anmap[list.size()] = i;
236           label = label + "_" + (index++);
237           list.add(label);
238           annotationLabels.put(annotation, label);
239         }
240       }
241     }
242     this.annmap = new int[list.size()];
243     System.arraycopy(anmap, 0, this.annmap, 0, this.annmap.length);
244     return list;
245   }
246
247   protected int getSelectedThresholdItem(int indexValue)
248   {
249     int selectedThresholdItem = -1;
250     if (indexValue == 1)
251     {
252       selectedThresholdItem = AnnotationColourGradient.ABOVE_THRESHOLD;
253     }
254     else if (indexValue == 2)
255     {
256       selectedThresholdItem = AnnotationColourGradient.BELOW_THRESHOLD;
257     }
258     return selectedThresholdItem;
259   }
260
261   public void ok_actionPerformed()
262   {
263     try
264     {
265       frame.setClosed(true);
266     } catch (Exception ex)
267     {
268     }
269   }
270
271   public void cancel_actionPerformed()
272   {
273     reset();
274     ap.paintAlignment(true);
275     try
276     {
277       frame.setClosed(true);
278     } catch (Exception ex)
279     {
280     }
281   }
282
283   protected void thresholdCheck_actionPerformed()
284   {
285     updateView();
286   }
287
288   protected void selectedAnnotationChanged()
289   {
290     updateView();
291   }
292
293   protected void threshold_actionPerformed()
294   {
295     updateView();
296   }
297
298   protected void thresholdValue_actionPerformed()
299   {
300     try
301     {
302       float f = Float.parseFloat(thresholdValue.getText());
303       if (percentThreshold.isSelected())
304       {
305         slider.setValue(slider.getMinimum() + ((int) ((f / 100f)
306                 * (slider.getMaximum() - slider.getMinimum()))));
307       }
308       else
309       {
310         slider.setValue((int) (f * 1000));
311       }
312       updateView();
313     } catch (NumberFormatException ex)
314     {
315     }
316   }
317
318   protected void percentageValue_actionPerformed()
319   {
320     setThresholdValueText();
321   }
322
323   protected void thresholdIsMin_actionPerformed()
324   {
325     updateView();
326   }
327
328   protected void populateThresholdComboBox(JComboBox<String> thresh)
329   {
330     thresh.addItem(MessageManager
331             .getString("label.threshold_feature_no_threshold"));
332     thresh.addItem(MessageManager
333             .getString("label.threshold_feature_above_threshold"));
334     thresh.addItem(MessageManager
335             .getString("label.threshold_feature_below_threshold"));
336   }
337
338   /**
339    * Rebuilds the drop-down list of annotations to choose from when the 'per
340    * sequence only' checkbox is checked or unchecked.
341    * 
342    * @param anns
343    */
344   protected void seqAssociated_actionPerformed(JComboBox<String> anns)
345   {
346     adjusting = true;
347     String cursel = (String) anns.getSelectedItem();
348     boolean isvalid = false;
349     boolean isseqs = seqAssociated.isSelected();
350     anns.removeAllItems();
351     for (String anitem : getAnnotationItems(seqAssociated.isSelected()))
352     {
353       if (anitem.equals(cursel) || (isseqs && cursel.startsWith(anitem)))
354       {
355         isvalid = true;
356         cursel = anitem;
357       }
358       anns.addItem(anitem);
359     }
360     if (isvalid)
361     {
362       anns.setSelectedItem(cursel);
363     }
364     else
365     {
366       if (anns.getItemCount() > 0)
367       {
368         anns.setSelectedIndex(0);
369       }
370     }
371     adjusting = false;
372
373     updateView();
374   }
375
376   protected void propagateSeqAssociatedThreshold(boolean allAnnotation,
377           AlignmentAnnotation annotation)
378   {
379     if (annotation.sequenceRef == null || annotation.threshold == null)
380     {
381       return;
382     }
383
384     float thr = annotation.threshold.value;
385     for (int i = 0; i < av.getAlignment()
386             .getAlignmentAnnotation().length; i++)
387     {
388       AlignmentAnnotation aa = av.getAlignment()
389               .getAlignmentAnnotation()[i];
390       if (aa.label.equals(annotation.label)
391               && (annotation.getCalcId() == null ? aa.getCalcId() == null
392                       : annotation.getCalcId().equals(aa.getCalcId())))
393       {
394         if (aa.threshold == null)
395         {
396           aa.threshold = new GraphLine(annotation.threshold);
397         }
398         else
399         {
400           aa.threshold.value = thr;
401         }
402       }
403     }
404   }
405
406   public AlignmentAnnotation getCurrentAnnotation()
407   {
408     return currentAnnotation;
409   }
410
411   protected void setCurrentAnnotation(AlignmentAnnotation annotation)
412   {
413     this.currentAnnotation = annotation;
414   }
415
416   protected abstract void valueChanged(boolean updateAllAnnotation);
417
418   protected abstract void updateView();
419
420   protected abstract void reset();
421
422   protected String getAnnotationMenuLabel(AlignmentAnnotation ann)
423   {
424     return annotationLabels.get(ann);
425   }
426
427   protected void jbInit()
428   {
429     ok.setOpaque(false);
430     ok.setText(MessageManager.getString("action.ok"));
431     ok.addActionListener(new ActionListener()
432     {
433       @Override
434       public void actionPerformed(ActionEvent e)
435       {
436         ok_actionPerformed();
437       }
438     });
439
440     cancel.setOpaque(false);
441     cancel.setText(MessageManager.getString("action.cancel"));
442     cancel.addActionListener(new ActionListener()
443     {
444       @Override
445       public void actionPerformed(ActionEvent e)
446       {
447         cancel_actionPerformed();
448       }
449     });
450
451     annotations.addItemListener(new ItemListener()
452     {
453       @Override
454       public void itemStateChanged(ItemEvent e)
455       {
456         selectedAnnotationChanged();
457       }
458     });
459     annotations.setToolTipText(
460             MessageManager.getString("info.select_annotation_row"));
461
462     threshold.addActionListener(new ActionListener()
463     {
464       @Override
465       public void actionPerformed(ActionEvent e)
466       {
467         threshold_actionPerformed();
468       }
469     });
470
471     thresholdValue.setEnabled(false);
472     thresholdValue.setColumns(7);
473     thresholdValue.addActionListener(new ActionListener()
474     {
475       @Override
476       public void actionPerformed(ActionEvent e)
477       {
478         thresholdValue_actionPerformed();
479       }
480     });
481
482     percentThreshold
483             .setText(MessageManager.getString("label.as_percentage"));
484     percentThreshold.addActionListener(new ActionListener()
485     {
486       @Override
487       public void actionPerformed(ActionEvent e)
488       {
489         if (!adjusting)
490         {
491           percentageValue_actionPerformed();
492         }
493       }
494     });
495     slider.setPaintLabels(false);
496     slider.setPaintTicks(true);
497     slider.setBackground(Color.white);
498     slider.setEnabled(false);
499     slider.setOpaque(false);
500     slider.setPreferredSize(new Dimension(100, 32));
501   }
502
503   public JComboBox<String> getThreshold()
504   {
505     return threshold;
506   }
507
508   public void setThreshold(JComboBox<String> thresh)
509   {
510     this.threshold = thresh;
511   }
512
513   public JComboBox<String> getAnnotations()
514   {
515     return annotations;
516   }
517
518   public void setAnnotations(JComboBox<String> anns)
519   {
520     this.annotations = anns;
521   }
522 }