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