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