package jalview.gui; import jalview.datamodel.AlignmentAnnotation; import jalview.datamodel.AlignmentI; import jalview.datamodel.SequenceGroup; import jalview.util.MessageManager; import java.awt.BorderLayout; import java.awt.Checkbox; import java.awt.CheckboxGroup; import java.awt.FlowLayout; import java.awt.Font; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.swing.JButton; import javax.swing.JInternalFrame; import javax.swing.JLayeredPane; import javax.swing.JPanel; /** * A panel that allows the user to select which sequence-associated annotation * rows to show or hide. * * @author gmcarstairs * */ @SuppressWarnings("serial") public class AnnotationChooser extends JPanel { private static final Font CHECKBOX_FONT = new Font("Serif", Font.BOLD, 12); private static final int MY_FRAME_WIDTH = 600; private static final int MY_FRAME_HEIGHT = 250; private JInternalFrame frame; private AlignmentPanel ap; private SequenceGroup sg; // all annotation rows' original visible state private boolean[] resetState = null; // is 'Show' selected? private boolean showSelected; // apply settings to selected (or all) sequences? private boolean applyToSelectedSequences; // apply settings to unselected (or all) sequences? private boolean applyToUnselectedSequences; // currently selected 'annotation type' checkboxes private Map selectedTypes = new HashMap(); /** * Constructor. * * @param alignPane */ public AnnotationChooser(AlignmentPanel alignPane) { super(); this.ap = alignPane; this.sg = alignPane.av.getSelectionGroup(); saveResetState(alignPane.getAlignment()); try { jbInit(); } catch (Exception ex) { ex.printStackTrace(); } showFrame(); } /** * Save the initial show/hide state of all annotations to allow a Cancel * operation. * * @param alignment */ protected void saveResetState(AlignmentI alignment) { AlignmentAnnotation[] annotations = alignment.getAlignmentAnnotation(); final int count = annotations.length; this.resetState = new boolean[count]; for (int i = 0; i < count; i++) { this.resetState[i] = annotations[i].visible; } } /** * Populate this frame with: *

* checkboxes for the types of annotation to show or hide (i.e. any annotation * type shown for any sequence in the whole alignment) *

* option to show or hide selected types *

* option to show/hide for the currently selected group, or its inverse *

* OK and Cancel (reset) buttons */ protected void jbInit() { setLayout(new GridLayout(3, 1)); add(buildAnnotationTypesPanel()); add(buildShowHideOptionsPanel()); add(buildActionButtonsPanel()); validate(); } /** * Construct the panel with checkboxes for annotation types. * * @return */ protected JPanel buildAnnotationTypesPanel() { JPanel jp = new JPanel(new FlowLayout(FlowLayout.LEFT)); List annotationTypes = getAnnotationTypes( this.ap.getAlignment(), true); for (final String type : annotationTypes) { final Checkbox check = new Checkbox(type); check.setFont(CHECKBOX_FONT); check.addItemListener(new ItemListener() { @Override public void itemStateChanged(ItemEvent evt) { if (evt.getStateChange() == ItemEvent.SELECTED) { AnnotationChooser.this.selectedTypes.put(type, type); } else { AnnotationChooser.this.selectedTypes.remove(type); } changeTypeSelected_actionPerformed(type); } }); jp.add(check); } return jp; } /** * Update display when scope (All/Selected sequences/Unselected) is changed. *

* Set annotations (with one of the selected types) to the selected Show/Hide * visibility, if they are in the new application scope. Set to the opposite * if outside the scope. *

* Note this only affects sequence-specific annotations, others are left * unchanged. */ protected void changeApplyTo_actionPerformed() { setAnnotationVisibility(true); // copied from AnnotationLabel.actionPerformed (after show/hide row)... // TODO should drive this functionality into AlignmentPanel ap.updateAnnotation(); // this.ap.annotationPanel.adjustPanelHeight(); // this.ap.alabels.setSize(this.ap.alabels.getSize().width, // this.ap.annotationPanel.getSize().height); // this.ap.validate(); this.ap.paintAlignment(true); } /** * Update display when an annotation type is selected or deselected. *

* If the type is selected, set visibility of annotations of that type which * are in the application scope (all, selected or unselected sequences). *

* If the type is unselected, set visibility to the opposite value. That is, * treat select/deselect as a 'toggle' operation. * * @param type */ protected void changeTypeSelected_actionPerformed(String type) { boolean typeSelected = this.selectedTypes.containsKey(type); for (AlignmentAnnotation aa : this.ap.getAlignment() .getAlignmentAnnotation()) { if (aa.sequenceRef != null && type.equals(aa.label) && isInActionScope(aa)) { aa.visible = typeSelected ? this.showSelected : !this.showSelected; } } ap.updateAnnotation(); // // this.ap.annotationPanel.adjustPanelHeight(); // this.ap.alabels.setSize(this.ap.alabels.getSize().width, // this.ap.annotationPanel.getSize().height); // this.ap.validate(); this.ap.paintAlignment(true); } /** * Update display on change of choice of Show or Hide *

* For annotations of any selected type, set visibility of annotations of that * type which are in the application scope (all, selected or unselected * sequences). * * @param type */ protected void changeShowHide_actionPerformed() { setAnnotationVisibility(false); this.ap.updateAnnotation(); // this.ap.annotationPanel.adjustPanelHeight(); this.ap.paintAlignment(true); } /** * Update visibility flags on annotation rows as per the current user choices. * * @param updateAllRows */ protected void setAnnotationVisibility(boolean updateAllRows) { for (AlignmentAnnotation aa : this.ap.getAlignment() .getAlignmentAnnotation()) { if (aa.sequenceRef != null) { setAnnotationVisibility(aa, updateAllRows); } } } /** * Determine and set the visibility of the given annotation from the currently * selected options. *

* Only update annotations whose type is one of the selected types. *

* If its sequence is in the selected application scope * (all/selected/unselected sequences), then we set its visibility according * to the current choice of Show or Hide. *

* If force update of all rows is wanted, then set rows not in the sequence * selection scope to the opposite visibility to those in scope. * * @param aa * @param updateAllRows */ protected void setAnnotationVisibility(AlignmentAnnotation aa, boolean updateAllRows) { if (this.selectedTypes.containsKey(aa.label)) { if (isInActionScope(aa)) { aa.visible = this.showSelected; } else if (updateAllRows) { aa.visible = !this.showSelected; } } // TODO force not visible if associated sequence is hidden? // currently hiding a sequence does not hide its annotation rows } /** * Answers true if the annotation falls in the current selection criteria for * show/hide. *

* It must be in the sequence selection group (for 'Apply to selection'), or * not in it (for 'Apply except to selection'). No check needed for 'Apply to * all'. * * @param aa * @return */ protected boolean isInActionScope(AlignmentAnnotation aa) { boolean result = false; if (this.applyToSelectedSequences && this.applyToUnselectedSequences) { // we don't care if the annotation's sequence is selected or not result = true; } else if (this.sg == null) { // shouldn't happen - defensive programming result = true; } else if (this.sg.getSequences().contains(aa.sequenceRef)) { // annotation is for a member of the selection group result = this.applyToSelectedSequences ? true : false; } else { // annotation is not associated with the selection group result = this.applyToUnselectedSequences ? true : false; } return result; } /** * Get annotation 'types' for an alignment, optionally restricted to * sequence-specific annotations only. The label is currently used for 'type'. * * TODO refactor to helper class. See * AnnotationColourChooser.getAnnotationItems() for another client * * @param alignment * @param sequenceSpecific * @return */ public static List getAnnotationTypes(AlignmentI alignment, boolean sequenceSpecificOnly) { List result = new ArrayList(); for (AlignmentAnnotation aa : alignment.getAlignmentAnnotation()) { if (!sequenceSpecificOnly || aa.sequenceRef != null) { String label = aa.label; if (!result.contains(label)) { result.add(label); } } } return result; } /** * Construct the panel with options to: *

* show or hide the selected annotation types *

* do this for the current selection group or its inverse * * @return */ protected JPanel buildShowHideOptionsPanel() { JPanel jp = new JPanel(); jp.setLayout(new BorderLayout()); JPanel showHideOptions = buildShowHidePanel(); jp.add(showHideOptions, BorderLayout.CENTER); JPanel applyToOptions = buildApplyToOptionsPanel(); jp.add(applyToOptions, BorderLayout.SOUTH); return jp; } /** * Build a panel with radio buttons options for sequences to apply show/hide * to. Options are all, current selection, all except current selection. * Initial state has 'current selection' selected. *

* If the sequence group is null, then we are acting on the whole alignment, * and only 'all sequences' is enabled (and selected). * * @return */ protected JPanel buildApplyToOptionsPanel() { final boolean wholeAlignment = this.sg == null; JPanel applyToOptions = new JPanel(new FlowLayout(FlowLayout.LEFT)); CheckboxGroup actingOn = new CheckboxGroup(); String forAll = MessageManager.getString("label.all_sequences"); final Checkbox allSequences = new Checkbox(forAll, actingOn, wholeAlignment); allSequences.addItemListener(new ItemListener() { @Override public void itemStateChanged(ItemEvent evt) { if (evt.getStateChange() == ItemEvent.SELECTED) { AnnotationChooser.this.setApplyToSelectedSequences(true); AnnotationChooser.this.setApplyToUnselectedSequences(true); AnnotationChooser.this.changeApplyTo_actionPerformed(); } } }); applyToOptions.add(allSequences); String forSelected = MessageManager .getString("label.selected_sequences"); final Checkbox selectedSequences = new Checkbox(forSelected, actingOn, !wholeAlignment); selectedSequences.setEnabled(!wholeAlignment); selectedSequences.addItemListener(new ItemListener() { @Override public void itemStateChanged(ItemEvent evt) { if (evt.getStateChange() == ItemEvent.SELECTED) { AnnotationChooser.this.setApplyToSelectedSequences(true); AnnotationChooser.this.setApplyToUnselectedSequences(false); AnnotationChooser.this.changeApplyTo_actionPerformed(); } } }); applyToOptions.add(selectedSequences); String exceptSelected = MessageManager .getString("label.except_selected_sequences"); final Checkbox unselectedSequences = new Checkbox(exceptSelected, actingOn, false); unselectedSequences.setEnabled(!wholeAlignment); unselectedSequences.addItemListener(new ItemListener() { @Override public void itemStateChanged(ItemEvent evt) { if (evt.getStateChange() == ItemEvent.SELECTED) { AnnotationChooser.this.setApplyToSelectedSequences(false); AnnotationChooser.this.setApplyToUnselectedSequences(true); AnnotationChooser.this.changeApplyTo_actionPerformed(); } } }); applyToOptions.add(unselectedSequences); // set member variables to match the initial selection state this.applyToSelectedSequences = selectedSequences.getState() || allSequences.getState(); this.applyToUnselectedSequences = unselectedSequences.getState() || allSequences.getState(); return applyToOptions; } /** * Build a panel with radio button options to show or hide selected * annotations. * * @return */ protected JPanel buildShowHidePanel() { JPanel showHideOptions = new JPanel(new FlowLayout(FlowLayout.LEFT)); CheckboxGroup showOrHide = new CheckboxGroup(); /* * Radio button 'Show selected annotations' - initially unselected */ String showLabel = MessageManager .getString("label.show_selected_annotations"); final Checkbox showOption = new Checkbox(showLabel, showOrHide, false); showOption.addItemListener(new ItemListener() { @Override public void itemStateChanged(ItemEvent evt) { if (evt.getStateChange() == ItemEvent.SELECTED) { AnnotationChooser.this.setShowSelected(true); AnnotationChooser.this.changeShowHide_actionPerformed(); } } }); showHideOptions.add(showOption); /* * Radio button 'hide selected annotations'- initially selected */ String hideLabel = MessageManager .getString("label.hide_selected_annotations"); final Checkbox hideOption = new Checkbox(hideLabel, showOrHide, true); hideOption.addItemListener(new ItemListener() { @Override public void itemStateChanged(ItemEvent evt) { if (evt.getStateChange() == ItemEvent.SELECTED) { AnnotationChooser.this.setShowSelected(false); AnnotationChooser.this.changeShowHide_actionPerformed(); } } }); showHideOptions.add(hideOption); /* * Set member variable to match initial selection state */ this.showSelected = showOption.getState(); return showHideOptions; } /** * Construct the panel with OK and Cancel buttons. * * @return */ protected JPanel buildActionButtonsPanel() { JPanel jp = new JPanel(); final Font labelFont = JvSwingUtils.getLabelFont(); JButton ok = new JButton(MessageManager.getString("action.ok")); ok.setFont(labelFont); ok.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { close_actionPerformed(); } }); jp.add(ok); JButton cancel = new JButton(MessageManager.getString("action.cancel")); cancel.setFont(labelFont); cancel.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { cancel_actionPerformed(); } }); jp.add(cancel); return jp; } /** * On 'Cancel' button, undo any changes. */ protected void cancel_actionPerformed() { resetOriginalState(); this.ap.repaint(); close_actionPerformed(); } /** * Restore annotation visibility to their state on entry here, and repaint * alignment. */ protected void resetOriginalState() { int i = 0; for (AlignmentAnnotation aa : this.ap.getAlignment() .getAlignmentAnnotation()) { aa.visible = this.resetState[i++]; } } /** * On 'Close' button, close the dialog. */ protected void close_actionPerformed() { try { this.frame.setClosed(true); } catch (Exception exe) { } } /** * Render a frame containing this panel. */ private void showFrame() { frame = new JInternalFrame(); frame.setContentPane(this); frame.setLayer(JLayeredPane.PALETTE_LAYER); Desktop.addInternalFrame(frame, MessageManager.getString("label.choose_annotations"), MY_FRAME_WIDTH, MY_FRAME_HEIGHT, true); } protected void setShowSelected(boolean showSelected) { this.showSelected = showSelected; } protected void setApplyToSelectedSequences( boolean applyToSelectedSequences) { this.applyToSelectedSequences = applyToSelectedSequences; } protected void setApplyToUnselectedSequences( boolean applyToUnselectedSequences) { this.applyToUnselectedSequences = applyToUnselectedSequences; } protected boolean isShowSelected() { return showSelected; } protected boolean isApplyToSelectedSequences() { return applyToSelectedSequences; } protected boolean isApplyToUnselectedSequences() { return applyToUnselectedSequences; } }