JAL-1553 implementation of column selection by annotation and threshold queries
[jalview.git] / src / jalview / gui / AnnotationColumnSelection.java
1 package jalview.gui;
2
3 import jalview.bin.Cache;
4 import jalview.datamodel.AlignmentAnnotation;
5 import jalview.datamodel.Annotation;
6 import jalview.datamodel.ColumnSelection;
7 import jalview.datamodel.GraphLine;
8 import jalview.schemes.AnnotationColourGradient;
9 import jalview.schemes.ColourSchemeI;
10 import jalview.util.MessageManager;
11
12 import java.awt.BorderLayout;
13 import java.awt.Color;
14 import java.awt.Dimension;
15 import java.awt.event.ActionEvent;
16 import java.awt.event.ActionListener;
17 import java.awt.event.MouseAdapter;
18 import java.awt.event.MouseEvent;
19 import java.util.Hashtable;
20 import java.util.Vector;
21
22 import javax.swing.JButton;
23 import javax.swing.JCheckBox;
24 import javax.swing.JComboBox;
25 import javax.swing.JInternalFrame;
26 import javax.swing.JLayeredPane;
27 import javax.swing.JPanel;
28 import javax.swing.JSlider;
29 import javax.swing.JTextField;
30 import javax.swing.event.ChangeEvent;
31 import javax.swing.event.ChangeListener;
32
33 import net.miginfocom.swing.MigLayout;
34
35 public class AnnotationColumnSelection extends JPanel
36 {
37   JInternalFrame frame;
38
39   AlignViewport av;
40
41   AlignmentPanel ap;
42
43   ColourSchemeI oldcs;
44
45   Hashtable oldgroupColours;
46
47   private JComboBox annotations;
48
49   int[] annmap;
50
51   JPanel minColour = new JPanel();
52
53   JPanel maxColour = new JPanel();
54
55   JButton defColours = new JButton();
56
57   JButton ok = new JButton();
58
59   JButton cancel = new JButton();
60
61   JPanel jPanel1 = new JPanel();
62
63   JPanel jPanel2 = new JPanel();
64
65   BorderLayout borderLayout1 = new BorderLayout();
66
67   private JComboBox threshold = new JComboBox();
68
69   JSlider slider = new JSlider();
70
71   JTextField thresholdValue = new JTextField(20);
72
73   JCheckBox currentColours = new JCheckBox();
74
75   JCheckBox thresholdIsMin = new JCheckBox();
76
77   JCheckBox seqAssociated = new JCheckBox();
78
79   private jalview.datamodel.AlignmentAnnotation currentAnnotation;
80
81   boolean adjusting = false;
82
83   /**
84    * enabled if the user is dragging the slider - try to keep updates to a
85    * minimun
86    */
87   boolean sliderDragging = false;
88
89   public AnnotationColumnSelection(AlignViewport av, final AlignmentPanel ap)
90   {
91
92     this.av = av;
93     this.ap = ap;
94     frame = new JInternalFrame();
95     frame.setContentPane(this);
96     frame.setLayer(JLayeredPane.PALETTE_LAYER);
97     Desktop.addInternalFrame(frame, "Select By Annotation", 520, 215);
98
99     slider.addChangeListener(new ChangeListener()
100     {
101       @Override
102       public void stateChanged(ChangeEvent evt)
103       {
104         if (!adjusting)
105         {
106           thresholdValue.setText((slider.getValue() / 1000f) + "");
107           valueChanged(!sliderDragging);
108         }
109       }
110     });
111     slider.addMouseListener(new MouseAdapter()
112     {
113       @Override
114       public void mousePressed(MouseEvent e)
115       {
116         sliderDragging = true;
117         super.mousePressed(e);
118       }
119
120       @Override
121       public void mouseDragged(MouseEvent e)
122       {
123         sliderDragging = true;
124         super.mouseDragged(e);
125       }
126
127       @Override
128       public void mouseReleased(MouseEvent evt)
129       {
130         if (sliderDragging)
131         {
132           sliderDragging = false;
133           valueChanged(true);
134         }
135         ap.paintAlignment(true);
136       }
137     });
138
139     if (av.getAlignment().getAlignmentAnnotation() == null)
140     {
141       return;
142     }
143
144     // Always get default shading from preferences.
145     setDefaultMinMax();
146
147     adjusting = true;
148
149     setAnnotations(new JComboBox(
150             getAnnotationItems(seqAssociated.isSelected())));
151
152     threshold.addItem(MessageManager
153             .getString("label.threshold_feature_no_thereshold"));
154     threshold.addItem(MessageManager
155             .getString("label.threshold_feature_above_thereshold"));
156     threshold.addItem(MessageManager
157             .getString("label.threshold_feature_below_thereshold"));
158
159     if (av.getCurrentAnnotationColumnSelectionState() != null)
160     {
161       annotations.setSelectedIndex(av
162               .getCurrentAnnotationColumnSelectionState().getAnnotations()
163               .getSelectedIndex());
164       threshold.setSelectedIndex(av
165               .getCurrentAnnotationColumnSelectionState().getThreshold()
166               .getSelectedIndex());
167       System.out.println("selected annotation : "
168               + av.getCurrentAnnotationColumnSelectionState()
169                       .getAnnotations().getSelectedIndex());
170       System.out.println("selected threshold : "
171               + av.getCurrentAnnotationColumnSelectionState()
172                       .getThreshold().getSelectedIndex());
173     }
174
175     try
176     {
177       jbInit();
178     } catch (Exception ex)
179     {
180     }
181
182     adjusting = false;
183
184     changeColumnSelection();
185     frame.invalidate();
186     frame.pack();
187
188   }
189
190   private Vector<String> getAnnotationItems(boolean isSeqAssociated)
191   {
192     Vector<String> list = new Vector<String>();
193     int index = 1;
194     int[] anmap = new int[av.getAlignment().getAlignmentAnnotation().length];
195     boolean enableSeqAss = false;
196     for (int i = 0; i < av.getAlignment().getAlignmentAnnotation().length; i++)
197     {
198       if (av.getAlignment().getAlignmentAnnotation()[i].sequenceRef == null)
199       {
200         if (isSeqAssociated)
201         {
202           continue;
203         }
204       }
205       else
206       {
207         enableSeqAss = true;
208       }
209       String label = av.getAlignment().getAlignmentAnnotation()[i].label;
210       if (!list.contains(label))
211       {
212         anmap[list.size()] = i;
213         list.add(label);
214
215       }
216       else
217       {
218         if (!isSeqAssociated)
219         {
220           anmap[list.size()] = i;
221           list.add(label + "_" + (index++));
222         }
223       }
224     }
225     seqAssociated.setEnabled(enableSeqAss);
226     this.annmap = new int[list.size()];
227     System.arraycopy(anmap, 0, this.annmap, 0, this.annmap.length);
228     return list;
229   }
230
231   private void setDefaultMinMax()
232   {
233     minColour.setBackground(Cache.getDefaultColour("ANNOTATIONCOLOUR_MIN",
234             Color.orange));
235     maxColour.setBackground(Cache.getDefaultColour("ANNOTATIONCOLOUR_MAX",
236             Color.red));
237   }
238
239   public AnnotationColumnSelection()
240   {
241     try
242     {
243       jbInit();
244     } catch (Exception ex)
245     {
246       ex.printStackTrace();
247     }
248   }
249
250   private void jbInit() throws Exception
251   {
252     ok.setOpaque(false);
253     ok.setText(MessageManager.getString("action.ok"));
254     ok.addActionListener(new ActionListener()
255     {
256       @Override
257       public void actionPerformed(ActionEvent e)
258       {
259         ok_actionPerformed(e);
260       }
261     });
262     cancel.setOpaque(false);
263     cancel.setText(MessageManager.getString("action.cancel"));
264     cancel.addActionListener(new ActionListener()
265     {
266       @Override
267       public void actionPerformed(ActionEvent e)
268       {
269         cancel_actionPerformed(e);
270       }
271     });
272
273     getAnnotations().addActionListener(new ActionListener()
274     {
275       @Override
276       public void actionPerformed(ActionEvent e)
277       {
278         annotations_actionPerformed(e);
279       }
280     });
281     getThreshold().addActionListener(new ActionListener()
282     {
283       @Override
284       public void actionPerformed(ActionEvent e)
285       {
286         threshold_actionPerformed(e);
287       }
288     });
289     thresholdValue.addActionListener(new ActionListener()
290     {
291       @Override
292       public void actionPerformed(ActionEvent e)
293       {
294         thresholdValue_actionPerformed(e);
295       }
296     });
297     slider.setPaintLabels(false);
298     slider.setPaintTicks(true);
299     slider.setBackground(Color.white);
300     slider.setEnabled(false);
301     slider.setOpaque(false);
302     slider.setPreferredSize(new Dimension(100, 32));
303     thresholdValue.setEnabled(false);
304     thresholdValue.setColumns(7);
305     thresholdIsMin.setBackground(Color.white);
306     thresholdIsMin.setFont(JvSwingUtils.getLabelFont());
307     thresholdIsMin.setText(MessageManager
308             .getString("label.threshold_minmax"));
309     thresholdIsMin.addActionListener(new ActionListener()
310     {
311       @Override
312       public void actionPerformed(ActionEvent actionEvent)
313       {
314         thresholdIsMin_actionPerformed(actionEvent);
315       }
316     });
317     seqAssociated.setBackground(Color.white);
318     seqAssociated.setFont(JvSwingUtils.getLabelFont());
319     seqAssociated.setText(MessageManager
320             .getString("label.per_sequence_only"));
321     seqAssociated.addActionListener(new ActionListener()
322     {
323
324       @Override
325       public void actionPerformed(ActionEvent arg0)
326       {
327         seqAssociated_actionPerformed(arg0);
328       }
329     });
330
331     this.setLayout(borderLayout1);
332     jPanel2.setLayout(new MigLayout("", "[left][center][right]", "[][][]"));
333     jPanel1.setBackground(Color.white);
334     jPanel2.setBackground(Color.white);
335
336     jPanel1.add(ok);
337     jPanel1.add(cancel);
338     jPanel2.add(getAnnotations(), "grow, wrap");
339     jPanel2.add(seqAssociated, "wrap");
340     jPanel2.add(getThreshold(), "grow, wrap");
341     jPanel2.add(thresholdIsMin, "wrap");
342     jPanel2.add(slider, "grow");
343     jPanel2.add(thresholdValue, "grow");
344     this.add(jPanel1, java.awt.BorderLayout.SOUTH);
345     this.add(jPanel2, java.awt.BorderLayout.CENTER);
346     this.validate();
347   }
348
349   protected void seqAssociated_actionPerformed(ActionEvent arg0)
350   {
351     adjusting = true;
352     String cursel = (String) getAnnotations().getSelectedItem();
353     boolean isvalid = false, isseqs = seqAssociated.isSelected();
354     this.getAnnotations().removeAllItems();
355     for (String anitem : getAnnotationItems(seqAssociated.isSelected()))
356     {
357       if (anitem.equals(cursel) || (isseqs && cursel.startsWith(anitem)))
358       {
359         isvalid = true;
360         cursel = anitem;
361       }
362       this.getAnnotations().addItem(anitem);
363     }
364     adjusting = false;
365     if (isvalid)
366     {
367       this.getAnnotations().setSelectedItem(cursel);
368     }
369     else
370     {
371       if (getAnnotations().getItemCount() > 0)
372       {
373         getAnnotations().setSelectedIndex(0);
374       }
375     }
376   }
377
378
379   void changeColumnSelection()
380   {
381     // Check if combobox is still adjusting
382     if (adjusting)
383     {
384       return;
385     }
386
387     setCurrentAnnotation(av.getAlignment().getAlignmentAnnotation()[annmap[getAnnotations()
388             .getSelectedIndex()]]);
389
390     int aboveThreshold = -1;
391     if (getThreshold().getSelectedIndex() == 1)
392     {
393       aboveThreshold = AnnotationColourGradient.ABOVE_THRESHOLD;
394     }
395     else if (getThreshold().getSelectedIndex() == 2)
396     {
397       aboveThreshold = AnnotationColourGradient.BELOW_THRESHOLD;
398     }
399
400     slider.setEnabled(true);
401     thresholdValue.setEnabled(true);
402     thresholdIsMin.setEnabled(true);
403
404     if (aboveThreshold == AnnotationColourGradient.NO_THRESHOLD)
405     {
406       slider.setEnabled(false);
407       thresholdValue.setEnabled(false);
408       thresholdValue.setText("");
409       thresholdIsMin.setEnabled(false);
410     }
411     else if (aboveThreshold != AnnotationColourGradient.NO_THRESHOLD
412             && getCurrentAnnotation().threshold == null)
413     {
414       getCurrentAnnotation()
415               .setThreshold(new jalview.datamodel.GraphLine(
416                               (getCurrentAnnotation().graphMax - getCurrentAnnotation().graphMin) / 2f,
417                       "Threshold", Color.black));
418     }
419
420     if (aboveThreshold != AnnotationColourGradient.NO_THRESHOLD)
421     {
422       adjusting = true;
423       float range = getCurrentAnnotation().graphMax * 1000
424               - getCurrentAnnotation().graphMin * 1000;
425
426       slider.setMinimum((int) (getCurrentAnnotation().graphMin * 1000));
427       slider.setMaximum((int) (getCurrentAnnotation().graphMax * 1000));
428       slider.setValue((int) (getCurrentAnnotation().threshold.value * 1000));
429       thresholdValue.setText(getCurrentAnnotation().threshold.value + "");
430       slider.setMajorTickSpacing((int) (range / 10f));
431       slider.setEnabled(true);
432       thresholdValue.setEnabled(true);
433       adjusting = false;
434     }
435
436     markColumnsContaining(getCurrentAnnotation(), aboveThreshold);
437     av.setCurrentAnnotationColumnSelectionState(this);
438     ap.alignmentChanged();
439     // ensure all associated views (overviews, structures, etc) are notified of
440     // updated colours.
441     ap.paintAlignment(true);
442   }
443
444   public boolean markColumnsContaining(
445           AlignmentAnnotation currentAnnotation, int thresholdComparisonType)
446   {
447     try
448     {
449       if (currentAnnotation != null)
450       {
451         Annotation[] annotations = currentAnnotation.annotations;
452         ColumnSelection cs = av.getColumnSelection();
453         cs.clear();
454         if (thresholdComparisonType == AnnotationColourGradient.NO_THRESHOLD)
455         {
456           int count = 0;
457           do
458           {
459             if (annotations[count] != null)
460             {
461               if (currentAnnotation.label.equals("Secondary Structure")
462                       && annotations[count].secondaryStructure != ' ')
463               {
464                 cs.addElement(count);
465               }
466               else if (currentAnnotation.label
467                       .equals("Iron Sulphur Contacts"))
468               {
469                 cs.addElement(count);
470               }
471               else if (annotations[count].value != 0.0)
472               {
473                 cs.addElement(count);
474               }
475
476             }
477             count++;
478           } while (count < annotations.length);
479         }
480         else
481         {
482           int count = 0;
483           do
484           {
485             if (annotations[count] != null)
486             {
487               if (thresholdComparisonType == AnnotationColourGradient.ABOVE_THRESHOLD)
488               {
489                 if (annotations[count].value > currentAnnotation.threshold.value)
490                 {
491                   cs.addElement(count);
492                 }
493               }
494               else if (thresholdComparisonType == AnnotationColourGradient.BELOW_THRESHOLD)
495               {
496                 if (annotations[count].value < currentAnnotation.threshold.value)
497                 {
498                   cs.addElement(count);
499                 }
500               }
501
502             }
503             count++;
504           } while (count < annotations.length);
505         }
506       }
507
508       return true;
509     } catch (Exception e)
510     {
511       e.printStackTrace();
512       return false;
513     }
514   }
515
516   public void ok_actionPerformed(ActionEvent e)
517   {
518     changeColumnSelection();
519     try
520     {
521       frame.setClosed(true);
522     } catch (Exception ex)
523     {
524     }
525   }
526
527   public void cancel_actionPerformed(ActionEvent e)
528   {
529     reset();
530     // ensure all original colouring is propagated to listeners.
531     ap.paintAlignment(true);
532     try
533     {
534       frame.setClosed(true);
535     } catch (Exception ex)
536     {
537     }
538   }
539
540   void reset()
541   {
542     av.getColumnSelection().clear();
543   }
544
545   public void thresholdCheck_actionPerformed(ActionEvent e)
546   {
547     changeColumnSelection();
548   }
549
550   public void annotations_actionPerformed(ActionEvent e)
551   {
552     changeColumnSelection();
553   }
554
555   public void threshold_actionPerformed(ActionEvent e)
556   {
557     changeColumnSelection();
558   }
559
560   public void thresholdValue_actionPerformed(ActionEvent e)
561   {
562     try
563     {
564       float f = Float.parseFloat(thresholdValue.getText());
565       slider.setValue((int) (f * 1000));
566       changeColumnSelection();
567     } catch (NumberFormatException ex)
568     {
569     }
570   }
571
572   public void valueChanged(boolean updateAllAnnotation)
573   {
574     getCurrentAnnotation().threshold.value = slider.getValue() / 1000f;
575     changeColumnSelection();
576     // propagateSeqAssociatedThreshold(updateAllAnnotation);
577     ap.paintAlignment(false);
578   }
579
580   private void propagateSeqAssociatedThreshold(boolean allAnnotation)
581   {
582     if (getCurrentAnnotation().sequenceRef == null
583             || getCurrentAnnotation().threshold == null)
584     {
585       return;
586     }
587
588
589     float thr = getCurrentAnnotation().threshold.value;
590     for (int i = 0; i < av.getAlignment().getAlignmentAnnotation().length; i++)
591     {
592       AlignmentAnnotation aa = av.getAlignment().getAlignmentAnnotation()[i];
593       if (aa.label.equals(getCurrentAnnotation().label)
594               && (getCurrentAnnotation().getCalcId() == null ? aa
595                       .getCalcId() == null : getCurrentAnnotation()
596                       .getCalcId()
597                               .equals(aa.getCalcId())))
598       {
599         if (aa.threshold == null)
600         {
601           aa.threshold = new GraphLine(getCurrentAnnotation().threshold);
602         }
603         else
604         {
605           aa.threshold.value = thr;
606         }
607       }
608     }
609   }
610
611   public void currentColours_actionPerformed(ActionEvent e)
612   {
613     if (currentColours.isSelected())
614     {
615       reset();
616     }
617
618     maxColour.setEnabled(!currentColours.isSelected());
619     minColour.setEnabled(!currentColours.isSelected());
620
621     changeColumnSelection();
622   }
623
624   public void thresholdIsMin_actionPerformed(ActionEvent actionEvent)
625   {
626     changeColumnSelection();
627   }
628
629   public jalview.datamodel.AlignmentAnnotation getCurrentAnnotation()
630   {
631     return currentAnnotation;
632   }
633
634   public void setCurrentAnnotation(
635           jalview.datamodel.AlignmentAnnotation currentAnnotation)
636   {
637     this.currentAnnotation = currentAnnotation;
638   }
639
640   public JComboBox getThreshold()
641   {
642     return threshold;
643   }
644
645   public void setThreshold(JComboBox threshold)
646   {
647     this.threshold = threshold;
648   }
649
650   public JComboBox getAnnotations()
651   {
652     return annotations;
653   }
654
655   public void setAnnotations(JComboBox annotations)
656   {
657     this.annotations = annotations;
658   }
659
660 }