JAL-1645 source formatting and organise imports
[jalview.git] / src / jalview / gui / AnnotationChooser.java
1 package jalview.gui;
2
3 import jalview.datamodel.AlignmentAnnotation;
4 import jalview.datamodel.AlignmentI;
5 import jalview.datamodel.SequenceGroup;
6 import jalview.util.MessageManager;
7
8 import java.awt.BorderLayout;
9 import java.awt.Checkbox;
10 import java.awt.CheckboxGroup;
11 import java.awt.FlowLayout;
12 import java.awt.Font;
13 import java.awt.GridLayout;
14 import java.awt.event.ActionEvent;
15 import java.awt.event.ActionListener;
16 import java.awt.event.ItemEvent;
17 import java.awt.event.ItemListener;
18 import java.util.ArrayList;
19 import java.util.HashMap;
20 import java.util.List;
21 import java.util.Map;
22
23 import javax.swing.JButton;
24 import javax.swing.JInternalFrame;
25 import javax.swing.JLayeredPane;
26 import javax.swing.JPanel;
27
28 /**
29  * A panel that allows the user to select which sequence-associated annotation
30  * rows to show or hide.
31  * 
32  * @author gmcarstairs
33  *
34  */
35 @SuppressWarnings("serial")
36 public class AnnotationChooser extends JPanel
37 {
38
39   private static final Font CHECKBOX_FONT = new Font("Serif", Font.BOLD, 12);
40
41   private static final int MY_FRAME_WIDTH = 600;
42
43   private static final int MY_FRAME_HEIGHT = 250;
44
45   private JInternalFrame frame;
46
47   private AlignmentPanel ap;
48
49   private SequenceGroup sg;
50
51   // all annotation rows' original visible state
52   private boolean[] resetState = null;
53
54   // is 'Show' selected?
55   private boolean showSelected;
56
57   // apply settings to selected (or all) sequences?
58   private boolean applyToSelectedSequences;
59
60   // apply settings to unselected (or all) sequences?
61   private boolean applyToUnselectedSequences;
62
63   // currently selected 'annotation type' checkboxes
64   private Map<String, String> selectedTypes = new HashMap<String, String>();
65
66   /**
67    * Constructor.
68    * 
69    * @param alignPane
70    */
71   public AnnotationChooser(AlignmentPanel alignPane)
72   {
73     super();
74     this.ap = alignPane;
75     this.sg = alignPane.av.getSelectionGroup();
76     saveResetState(alignPane.getAlignment());
77
78     try
79     {
80       jbInit();
81     } catch (Exception ex)
82     {
83       ex.printStackTrace();
84     }
85     showFrame();
86   }
87
88   /**
89    * Save the initial show/hide state of all annotations to allow a Cancel
90    * operation.
91    * 
92    * @param alignment
93    */
94   protected void saveResetState(AlignmentI alignment)
95   {
96     AlignmentAnnotation[] annotations = alignment.getAlignmentAnnotation();
97     final int count = annotations.length;
98     this.resetState = new boolean[count];
99     for (int i = 0; i < count; i++)
100     {
101       this.resetState[i] = annotations[i].visible;
102     }
103   }
104
105   /**
106    * Populate this frame with:
107    * <p>
108    * checkboxes for the types of annotation to show or hide (i.e. any annotation
109    * type shown for any sequence in the whole alignment)
110    * <p>
111    * option to show or hide selected types
112    * <p>
113    * option to show/hide for the currently selected group, or its inverse
114    * <p>
115    * OK and Cancel (reset) buttons
116    */
117   protected void jbInit()
118   {
119     setLayout(new GridLayout(3, 1));
120     add(buildAnnotationTypesPanel());
121     add(buildShowHideOptionsPanel());
122     add(buildActionButtonsPanel());
123     validate();
124   }
125
126   /**
127    * Construct the panel with checkboxes for annotation types.
128    * 
129    * @return
130    */
131   protected JPanel buildAnnotationTypesPanel()
132   {
133     JPanel jp = new JPanel(new FlowLayout(FlowLayout.LEFT));
134
135     List<String> annotationTypes = getAnnotationTypes(
136             this.ap.getAlignment(), true);
137
138     for (final String type : annotationTypes)
139     {
140       final Checkbox check = new Checkbox(type);
141       check.setFont(CHECKBOX_FONT);
142       check.addItemListener(new ItemListener()
143       {
144         @Override
145         public void itemStateChanged(ItemEvent evt)
146         {
147           if (evt.getStateChange() == ItemEvent.SELECTED)
148           {
149             AnnotationChooser.this.selectedTypes.put(type, type);
150           }
151           else
152           {
153             AnnotationChooser.this.selectedTypes.remove(type);
154           }
155           changeTypeSelected_actionPerformed(type);
156         }
157       });
158       jp.add(check);
159     }
160     return jp;
161   }
162
163   /**
164    * Update display when scope (All/Selected sequences/Unselected) is changed.
165    * <p>
166    * Set annotations (with one of the selected types) to the selected Show/Hide
167    * visibility, if they are in the new application scope. Set to the opposite
168    * if outside the scope.
169    * <p>
170    * Note this only affects sequence-specific annotations, others are left
171    * unchanged.
172    */
173   protected void changeApplyTo_actionPerformed()
174   {
175     setAnnotationVisibility(true);
176
177     // copied from AnnotationLabel.actionPerformed (after show/hide row)...
178     // TODO should drive this functionality into AlignmentPanel
179     ap.updateAnnotation();
180     // this.ap.annotationPanel.adjustPanelHeight();
181     // this.ap.alabels.setSize(this.ap.alabels.getSize().width,
182     // this.ap.annotationPanel.getSize().height);
183     // this.ap.validate();
184     this.ap.paintAlignment(true);
185   }
186
187   /**
188    * Update display when an annotation type is selected or deselected.
189    * <p>
190    * If the type is selected, set visibility of annotations of that type which
191    * are in the application scope (all, selected or unselected sequences).
192    * <p>
193    * If the type is unselected, set visibility to the opposite value. That is,
194    * treat select/deselect as a 'toggle' operation.
195    * 
196    * @param type
197    */
198   protected void changeTypeSelected_actionPerformed(String type)
199   {
200     boolean typeSelected = this.selectedTypes.containsKey(type);
201     for (AlignmentAnnotation aa : this.ap.getAlignment()
202             .getAlignmentAnnotation())
203     {
204       if (aa.sequenceRef != null && type.equals(aa.label)
205               && isInActionScope(aa))
206       {
207         aa.visible = typeSelected ? this.showSelected : !this.showSelected;
208       }
209     }
210     ap.updateAnnotation();
211     // // this.ap.annotationPanel.adjustPanelHeight();
212     // this.ap.alabels.setSize(this.ap.alabels.getSize().width,
213     // this.ap.annotationPanel.getSize().height);
214     // this.ap.validate();
215     this.ap.paintAlignment(true);
216   }
217
218   /**
219    * Update display on change of choice of Show or Hide
220    * <p>
221    * For annotations of any selected type, set visibility of annotations of that
222    * type which are in the application scope (all, selected or unselected
223    * sequences).
224    * 
225    * @param type
226    */
227   protected void changeShowHide_actionPerformed()
228   {
229     setAnnotationVisibility(false);
230
231     this.ap.updateAnnotation();
232     // this.ap.annotationPanel.adjustPanelHeight();
233     this.ap.paintAlignment(true);
234   }
235
236   /**
237    * Update visibility flags on annotation rows as per the current user choices.
238    * 
239    * @param updateAllRows
240    */
241   protected void setAnnotationVisibility(boolean updateAllRows)
242   {
243     for (AlignmentAnnotation aa : this.ap.getAlignment()
244             .getAlignmentAnnotation())
245     {
246       if (aa.sequenceRef != null)
247       {
248         setAnnotationVisibility(aa, updateAllRows);
249       }
250     }
251   }
252
253   /**
254    * Determine and set the visibility of the given annotation from the currently
255    * selected options.
256    * <p>
257    * Only update annotations whose type is one of the selected types.
258    * <p>
259    * If its sequence is in the selected application scope
260    * (all/selected/unselected sequences), then we set its visibility according
261    * to the current choice of Show or Hide.
262    * <p>
263    * If force update of all rows is wanted, then set rows not in the sequence
264    * selection scope to the opposite visibility to those in scope.
265    * 
266    * @param aa
267    * @param updateAllRows
268    */
269   protected void setAnnotationVisibility(AlignmentAnnotation aa,
270           boolean updateAllRows)
271   {
272     if (this.selectedTypes.containsKey(aa.label))
273     {
274       if (isInActionScope(aa))
275       {
276         aa.visible = this.showSelected;
277       }
278       else if (updateAllRows)
279       {
280         aa.visible = !this.showSelected;
281       }
282     }
283     // TODO force not visible if associated sequence is hidden?
284     // currently hiding a sequence does not hide its annotation rows
285   }
286
287   /**
288    * Answers true if the annotation falls in the current selection criteria for
289    * show/hide.
290    * <p>
291    * It must be in the sequence selection group (for 'Apply to selection'), or
292    * not in it (for 'Apply except to selection'). No check needed for 'Apply to
293    * all'.
294    * 
295    * @param aa
296    * @return
297    */
298   protected boolean isInActionScope(AlignmentAnnotation aa)
299   {
300     boolean result = false;
301     if (this.applyToSelectedSequences && this.applyToUnselectedSequences)
302     {
303       // we don't care if the annotation's sequence is selected or not
304       result = true;
305     }
306     else if (this.sg == null)
307     {
308       // shouldn't happen - defensive programming
309       result = true;
310     }
311     else if (this.sg.getSequences().contains(aa.sequenceRef))
312     {
313       // annotation is for a member of the selection group
314       result = this.applyToSelectedSequences ? true : false;
315     }
316     else
317     {
318       // annotation is not associated with the selection group
319       result = this.applyToUnselectedSequences ? true : false;
320     }
321     return result;
322   }
323
324   /**
325    * Get annotation 'types' for an alignment, optionally restricted to
326    * sequence-specific annotations only. The label is currently used for 'type'.
327    * 
328    * TODO refactor to helper class. See
329    * AnnotationColourChooser.getAnnotationItems() for another client
330    * 
331    * @param alignment
332    * @param sequenceSpecific
333    * @return
334    */
335   public static List<String> getAnnotationTypes(AlignmentI alignment,
336           boolean sequenceSpecificOnly)
337   {
338     List<String> result = new ArrayList<String>();
339     for (AlignmentAnnotation aa : alignment.getAlignmentAnnotation())
340     {
341       if (!sequenceSpecificOnly || aa.sequenceRef != null)
342       {
343         String label = aa.label;
344         if (!result.contains(label))
345         {
346           result.add(label);
347         }
348       }
349     }
350     return result;
351   }
352
353   /**
354    * Construct the panel with options to:
355    * <p>
356    * show or hide the selected annotation types
357    * <p>
358    * do this for the current selection group or its inverse
359    * 
360    * @return
361    */
362   protected JPanel buildShowHideOptionsPanel()
363   {
364     JPanel jp = new JPanel();
365     jp.setLayout(new BorderLayout());
366
367     JPanel showHideOptions = buildShowHidePanel();
368     jp.add(showHideOptions, BorderLayout.CENTER);
369
370     JPanel applyToOptions = buildApplyToOptionsPanel();
371     jp.add(applyToOptions, BorderLayout.SOUTH);
372
373     return jp;
374   }
375
376   /**
377    * Build a panel with radio buttons options for sequences to apply show/hide
378    * to. Options are all, current selection, all except current selection.
379    * Initial state has 'current selection' selected.
380    * <p>
381    * If the sequence group is null, then we are acting on the whole alignment,
382    * and only 'all sequences' is enabled (and selected).
383    * 
384    * @return
385    */
386   protected JPanel buildApplyToOptionsPanel()
387   {
388     final boolean wholeAlignment = this.sg == null;
389     JPanel applyToOptions = new JPanel(new FlowLayout(FlowLayout.LEFT));
390     CheckboxGroup actingOn = new CheckboxGroup();
391
392     String forAll = MessageManager.getString("label.all_sequences");
393     final Checkbox allSequences = new Checkbox(forAll, actingOn,
394             wholeAlignment);
395     allSequences.addItemListener(new ItemListener()
396     {
397       @Override
398       public void itemStateChanged(ItemEvent evt)
399       {
400         if (evt.getStateChange() == ItemEvent.SELECTED)
401         {
402           AnnotationChooser.this.setApplyToSelectedSequences(true);
403           AnnotationChooser.this.setApplyToUnselectedSequences(true);
404           AnnotationChooser.this.changeApplyTo_actionPerformed();
405         }
406       }
407     });
408     applyToOptions.add(allSequences);
409
410     String forSelected = MessageManager
411             .getString("label.selected_sequences");
412     final Checkbox selectedSequences = new Checkbox(forSelected, actingOn,
413             !wholeAlignment);
414     selectedSequences.setEnabled(!wholeAlignment);
415     selectedSequences.addItemListener(new ItemListener()
416     {
417       @Override
418       public void itemStateChanged(ItemEvent evt)
419       {
420         if (evt.getStateChange() == ItemEvent.SELECTED)
421         {
422           AnnotationChooser.this.setApplyToSelectedSequences(true);
423           AnnotationChooser.this.setApplyToUnselectedSequences(false);
424           AnnotationChooser.this.changeApplyTo_actionPerformed();
425         }
426       }
427     });
428     applyToOptions.add(selectedSequences);
429
430     String exceptSelected = MessageManager
431             .getString("label.except_selected_sequences");
432     final Checkbox unselectedSequences = new Checkbox(exceptSelected,
433             actingOn, false);
434     unselectedSequences.setEnabled(!wholeAlignment);
435     unselectedSequences.addItemListener(new ItemListener()
436     {
437       @Override
438       public void itemStateChanged(ItemEvent evt)
439       {
440         if (evt.getStateChange() == ItemEvent.SELECTED)
441         {
442           AnnotationChooser.this.setApplyToSelectedSequences(false);
443           AnnotationChooser.this.setApplyToUnselectedSequences(true);
444           AnnotationChooser.this.changeApplyTo_actionPerformed();
445         }
446       }
447     });
448     applyToOptions.add(unselectedSequences);
449
450     // set member variables to match the initial selection state
451     this.applyToSelectedSequences = selectedSequences.getState()
452             || allSequences.getState();
453     this.applyToUnselectedSequences = unselectedSequences.getState()
454             || allSequences.getState();
455
456     return applyToOptions;
457   }
458
459   /**
460    * Build a panel with radio button options to show or hide selected
461    * annotations.
462    * 
463    * @return
464    */
465   protected JPanel buildShowHidePanel()
466   {
467     JPanel showHideOptions = new JPanel(new FlowLayout(FlowLayout.LEFT));
468     CheckboxGroup showOrHide = new CheckboxGroup();
469
470     /*
471      * Radio button 'Show selected annotations' - initially unselected
472      */
473     String showLabel = MessageManager
474             .getString("label.show_selected_annotations");
475     final Checkbox showOption = new Checkbox(showLabel, showOrHide, false);
476     showOption.addItemListener(new ItemListener()
477     {
478       @Override
479       public void itemStateChanged(ItemEvent evt)
480       {
481         if (evt.getStateChange() == ItemEvent.SELECTED)
482         {
483           AnnotationChooser.this.setShowSelected(true);
484           AnnotationChooser.this.changeShowHide_actionPerformed();
485         }
486       }
487     });
488     showHideOptions.add(showOption);
489
490     /*
491      * Radio button 'hide selected annotations'- initially selected
492      */
493     String hideLabel = MessageManager
494             .getString("label.hide_selected_annotations");
495     final Checkbox hideOption = new Checkbox(hideLabel, showOrHide, true);
496     hideOption.addItemListener(new ItemListener()
497     {
498       @Override
499       public void itemStateChanged(ItemEvent evt)
500       {
501         if (evt.getStateChange() == ItemEvent.SELECTED)
502         {
503           AnnotationChooser.this.setShowSelected(false);
504           AnnotationChooser.this.changeShowHide_actionPerformed();
505         }
506       }
507     });
508     showHideOptions.add(hideOption);
509
510     /*
511      * Set member variable to match initial selection state
512      */
513     this.showSelected = showOption.getState();
514
515     return showHideOptions;
516   }
517
518   /**
519    * Construct the panel with OK and Cancel buttons.
520    * 
521    * @return
522    */
523   protected JPanel buildActionButtonsPanel()
524   {
525     JPanel jp = new JPanel();
526     final Font labelFont = JvSwingUtils.getLabelFont();
527
528     JButton ok = new JButton(MessageManager.getString("action.ok"));
529     ok.setFont(labelFont);
530     ok.addActionListener(new ActionListener()
531     {
532       @Override
533       public void actionPerformed(ActionEvent e)
534       {
535         close_actionPerformed();
536       }
537     });
538     jp.add(ok);
539
540     JButton cancel = new JButton(MessageManager.getString("action.cancel"));
541     cancel.setFont(labelFont);
542     cancel.addActionListener(new ActionListener()
543     {
544       @Override
545       public void actionPerformed(ActionEvent e)
546       {
547         cancel_actionPerformed();
548       }
549     });
550     jp.add(cancel);
551
552     return jp;
553   }
554
555   /**
556    * On 'Cancel' button, undo any changes.
557    */
558   protected void cancel_actionPerformed()
559   {
560     resetOriginalState();
561     this.ap.repaint();
562     close_actionPerformed();
563   }
564
565   /**
566    * Restore annotation visibility to their state on entry here, and repaint
567    * alignment.
568    */
569   protected void resetOriginalState()
570   {
571     int i = 0;
572     for (AlignmentAnnotation aa : this.ap.getAlignment()
573             .getAlignmentAnnotation())
574     {
575       aa.visible = this.resetState[i++];
576     }
577   }
578
579   /**
580    * On 'Close' button, close the dialog.
581    */
582   protected void close_actionPerformed()
583   {
584     try
585     {
586       this.frame.setClosed(true);
587     } catch (Exception exe)
588     {
589     }
590   }
591
592   /**
593    * Render a frame containing this panel.
594    */
595   private void showFrame()
596   {
597     frame = new JInternalFrame();
598     frame.setContentPane(this);
599     frame.setLayer(JLayeredPane.PALETTE_LAYER);
600     Desktop.addInternalFrame(frame,
601             MessageManager.getString("label.choose_annotations"),
602             MY_FRAME_WIDTH, MY_FRAME_HEIGHT, true);
603   }
604
605   protected void setShowSelected(boolean showSelected)
606   {
607     this.showSelected = showSelected;
608   }
609
610   protected void setApplyToSelectedSequences(
611           boolean applyToSelectedSequences)
612   {
613     this.applyToSelectedSequences = applyToSelectedSequences;
614   }
615
616   protected void setApplyToUnselectedSequences(
617           boolean applyToUnselectedSequences)
618   {
619     this.applyToUnselectedSequences = applyToUnselectedSequences;
620   }
621
622   protected boolean isShowSelected()
623   {
624     return showSelected;
625   }
626
627   protected boolean isApplyToSelectedSequences()
628   {
629     return applyToSelectedSequences;
630   }
631
632   protected boolean isApplyToUnselectedSequences()
633   {
634     return applyToUnselectedSequences;
635   }
636
637 }