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