X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fgui%2FAnnotationChooser.java;fp=src%2Fjalview%2Fgui%2FAnnotationChooser.java;h=bb940e8e5544ba0f7fb0420acaca5ece8ed325f6;hb=76d3286a73053be49a8cfd6842fffba4db111e08;hp=0000000000000000000000000000000000000000;hpb=55fc8f004dac252b5b89ac91cf506e4844a6b1da;p=jalview.git diff --git a/src/jalview/gui/AnnotationChooser.java b/src/jalview/gui/AnnotationChooser.java new file mode 100644 index 0000000..bb940e8 --- /dev/null +++ b/src/jalview/gui/AnnotationChooser.java @@ -0,0 +1,564 @@ +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.JCheckBox; +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 + * + */ +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 JCheckBox check = new JCheckBox(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); + } + repaintAnnotations(false); + } + }); + jp.add(check); + } + return jp; + } + + /** + * Set visibility flags on annotation rows then repaint the alignment panel. + *

+ * Optionally, update all rows, including those not in the 'apply to' scope. + * This makes more sense when switching between selected and unselected + * sequences. When selecting annotation types, or show/hide, we only apply the + * settings to the selected sequences. + *

+ * Note this only affects sequence-specific annotations, others are left + * unchanged. + */ + protected void repaintAnnotations(boolean updateAllRows) + { + for (AlignmentAnnotation aa : this.ap.getAlignment() + .getAlignmentAnnotation()) + { + if (aa.sequenceRef != null) + { + setAnnotationVisibility(aa, updateAllRows); + } + } + // 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); + } + + /** + * Determine and set the visibility of the given annotation from the currently + * selected options. + *

+ * If its sequence is in the selected application scope + * (all/selected/unselected sequences), then we set its visibility. + *

+ * If the annotation type is one of those currently selected by checkbox, set + * its visibility to the selected value. If it is not currently selected, set + * it to the opposite value. So, unselecting an annotation type with 'hide' + * selected, will cause those annotations to be unhidden. + *

+ * 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) + { + boolean setToVisible = false; + if (this.selectedTypes.containsKey(aa.label)) + { + setToVisible = this.showSelected; + } + else + { + setToVisible = !this.showSelected; + } + if (isInActionScope(aa)) + { + aa.visible = setToVisible; + } + else if (updateAllRows) + { + aa.visible = !setToVisible; + } + // 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.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) + { + // stub + 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.repaintAnnotations(true); + } + } + }); + 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.repaintAnnotations(true); + } + } + }); + 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.repaintAnnotations(true); + } + } + }); + 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 buttons 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.repaintAnnotations(false); + } + } + }); + 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.repaintAnnotations(false); + } + } + }); + 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; + } + +}