JAL-1264 further refactoring of panel + unit tests
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Wed, 24 Sep 2014 15:19:52 +0000 (16:19 +0100)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Wed, 24 Sep 2014 15:19:52 +0000 (16:19 +0100)
src/jalview/gui/AnnotationChooser.java
test/jalview/gui/AnnotationChooserTest.java

index bb940e8..33c4992 100644 (file)
@@ -21,7 +21,6 @@ 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;
@@ -33,6 +32,7 @@ import javax.swing.JPanel;
  * @author gmcarstairs
  *
  */
+@SuppressWarnings("serial")
 public class AnnotationChooser extends JPanel
 {
 
@@ -137,7 +137,7 @@ public class AnnotationChooser extends JPanel
 
     for (final String type : annotationTypes)
     {
-      final JCheckBox check = new JCheckBox(type);
+      final Checkbox check = new Checkbox(type);
       check.setFont(CHECKBOX_FONT);
       check.addItemListener(new ItemListener()
       {
@@ -152,7 +152,7 @@ public class AnnotationChooser extends JPanel
           {
             AnnotationChooser.this.selectedTypes.remove(type);
           }
-          repaintAnnotations(false);
+          changeTypeSelected_actionPerformed(type);
         }
       });
       jp.add(check);
@@ -161,47 +161,104 @@ public class AnnotationChooser extends JPanel
   }
 
   /**
-   * Set visibility flags on annotation rows then repaint the alignment panel.
+   * Update display when scope (All/Selected sequences/Unselected) is changed.
    * <p>
-   * 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.
+   * 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.
    * <p>
    * Note this only affects sequence-specific annotations, others are left
    * unchanged.
    */
-  protected void repaintAnnotations(boolean updateAllRows)
+  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.
+   * <p>
+   * If the type is selected, set visibility of annotations of that type which
+   * are in the application scope (all, selected or unselected sequences).
+   * <p>
+   * 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)
+      if (aa.sequenceRef != null && type.equals(aa.label)
+              && isInActionScope(aa))
       {
-        setAnnotationVisibility(aa, updateAllRows);
+        aa.visible = typeSelected ? this.showSelected : !this.showSelected;
       }
     }
-    // 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.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
+   * <p>
+   * 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.
    * <p>
-   * If its sequence is in the selected application scope
-   * (all/selected/unselected sequences), then we set its visibility.
+   * Only update annotations whose type is one of the selected types.
    * <p>
-   * 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 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.
    * <p>
    * 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.
@@ -212,22 +269,16 @@ public class AnnotationChooser extends JPanel
   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;
+      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
@@ -252,6 +303,11 @@ public class AnnotationChooser extends JPanel
       // 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
@@ -279,7 +335,6 @@ public class AnnotationChooser extends JPanel
   public static List<String> getAnnotationTypes(AlignmentI alignment,
           boolean sequenceSpecificOnly)
   {
-    // stub
     List<String> result = new ArrayList<String>();
     for (AlignmentAnnotation aa : alignment.getAlignmentAnnotation())
     {
@@ -345,7 +400,7 @@ public class AnnotationChooser extends JPanel
         if (evt.getStateChange() == ItemEvent.SELECTED) {
           AnnotationChooser.this.setApplyToSelectedSequences(true);
           AnnotationChooser.this.setApplyToUnselectedSequences(true);
-          AnnotationChooser.this.repaintAnnotations(true);
+          AnnotationChooser.this.changeApplyTo_actionPerformed();
         }
       }
     });
@@ -365,7 +420,7 @@ public class AnnotationChooser extends JPanel
         {
           AnnotationChooser.this.setApplyToSelectedSequences(true);
           AnnotationChooser.this.setApplyToUnselectedSequences(false);
-          AnnotationChooser.this.repaintAnnotations(true);
+          AnnotationChooser.this.changeApplyTo_actionPerformed();
         }
       }
     });
@@ -384,7 +439,7 @@ public class AnnotationChooser extends JPanel
         {
           AnnotationChooser.this.setApplyToSelectedSequences(false);
           AnnotationChooser.this.setApplyToUnselectedSequences(true);
-          AnnotationChooser.this.repaintAnnotations(true);
+          AnnotationChooser.this.changeApplyTo_actionPerformed();
         }
       }
     });
@@ -400,7 +455,7 @@ public class AnnotationChooser extends JPanel
   }
 
   /**
-   * Build a panel with radio buttons options to show or hide selected
+   * Build a panel with radio button options to show or hide selected
    * annotations.
    * 
    * @return
@@ -423,7 +478,7 @@ public class AnnotationChooser extends JPanel
       {
         if (evt.getStateChange() == ItemEvent.SELECTED) {
           AnnotationChooser.this.setShowSelected(true);
-          AnnotationChooser.this.repaintAnnotations(false);
+          AnnotationChooser.this.changeShowHide_actionPerformed();
         }
       }
     });
@@ -443,7 +498,7 @@ public class AnnotationChooser extends JPanel
         if (evt.getStateChange() == ItemEvent.SELECTED)
         {
           AnnotationChooser.this.setShowSelected(false);
-          AnnotationChooser.this.repaintAnnotations(false);
+          AnnotationChooser.this.changeShowHide_actionPerformed();
         }
       }
     });
@@ -561,4 +616,19 @@ public class AnnotationChooser extends JPanel
     this.applyToUnselectedSequences = applyToUnselectedSequences;
   }
 
+  protected boolean isShowSelected()
+  {
+    return showSelected;
+  }
+
+  protected boolean isApplyToSelectedSequences()
+  {
+    return applyToSelectedSequences;
+  }
+
+  protected boolean isApplyToUnselectedSequences()
+  {
+    return applyToUnselectedSequences;
+  }
+
 }
index 161ebee..944ab9c 100644 (file)
@@ -1,7 +1,27 @@
 package jalview.gui;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.Annotation;
+import jalview.datamodel.SequenceGroup;
+import jalview.datamodel.SequenceI;
 import jalview.io.AppletFormatAdapter;
-import jalview.io.FileLoader;
+import jalview.util.MessageManager;
+
+import java.awt.BorderLayout;
+import java.awt.Checkbox;
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.FlowLayout;
+import java.awt.event.ItemEvent;
+import java.io.IOException;
+import java.util.List;
+
+import javax.swing.JButton;
+import javax.swing.JPanel;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -14,31 +34,721 @@ import org.junit.Test;
  */
 public class AnnotationChooserTest
 {
-  final static String TEST_FILE = "./examples/uniref50.fa";
+  // 4 sequences x 13 positions
+  final static String TEST_DATA = ">FER_CAPAA Ferredoxin\n"
+          + "TIETHKEAELVG-\n"
+          + ">FER_CAPAN Ferredoxin, chloroplast precursor\n"
+          + "TIETHKEAELVG-\n"
+          + ">FER1_SOLLC Ferredoxin-1, chloroplast precursor\n"
+          + "TIETHKEEELTA-\n" + ">Q93XJ9_SOLTU Ferredoxin I precursor\n"
+          + "TIETHKEEELTA-\n";
+
   AnnotationChooser testee;
 
+  AlignmentPanel parentPanel;
+
   AlignFrame af;
 
   @Before
-  public void setUp()
+  public void setUp() throws IOException
+  {
+    AlignmentI al = new jalview.io.FormatAdapter().readFile(TEST_DATA,
+            AppletFormatAdapter.PASTE, "FASTA");
+    af = new AlignFrame(al, 700, 500);
+    parentPanel = new AlignmentPanel(af, af.getViewport());
+    addAnnotations();
+  }
+
+  /**
+   * Add 4 annotations, 3 of them sequence-specific.
+   * 
+   * <PRE>
+   * ann1 - for sequence 0 - label 'IUPRED' 
+   * ann2 - not sequence related - label 'Beauty' 
+   * ann3 - for sequence 3 - label 'JMol'
+   * ann4 - for sequence 2 - label 'IUPRED'
+   * ann5 - for sequence 1 - label 'JMol'
+   */
+  private void addAnnotations()
+  {
+    Annotation an = new Annotation(2f);
+    Annotation[] anns = new Annotation[]
+    { an, an, an };
+    AlignmentAnnotation ann0 = new AlignmentAnnotation("IUPRED", "", anns);
+    AlignmentAnnotation ann1 = new AlignmentAnnotation("Beauty", "", anns);
+    AlignmentAnnotation ann2 = new AlignmentAnnotation("JMol", "", anns);
+    AlignmentAnnotation ann3 = new AlignmentAnnotation("IUPRED", "", anns);
+    AlignmentAnnotation ann4 = new AlignmentAnnotation("JMol", "", anns);
+    SequenceI[] seqs = parentPanel.getAlignment().getSequencesArray();
+    ann0.setSequenceRef(seqs[0]);
+    ann2.setSequenceRef(seqs[3]);
+    ann3.setSequenceRef(seqs[2]);
+    ann4.setSequenceRef(seqs[1]);
+    parentPanel.getAlignment().addAnnotation(ann0);
+    parentPanel.getAlignment().addAnnotation(ann1);
+    parentPanel.getAlignment().addAnnotation(ann2);
+    parentPanel.getAlignment().addAnnotation(ann3);
+    parentPanel.getAlignment().addAnnotation(ann4);
+  }
+
+  /**
+   * Test creation of panel with OK and Cancel buttons
+   */
+  @Test
+  public void testBuildActionButtonsPanel()
+  {
+    testee = new AnnotationChooser(parentPanel);
+    JPanel jp = testee.buildActionButtonsPanel();
+    assertTrue("Wrong layout", jp.getLayout() instanceof FlowLayout);
+
+    Component[] comps = jp.getComponents();
+    assertEquals("Not 2 action buttons", 2, comps.length);
+
+    final Component jb1 = comps[0];
+    final Component jb2 = comps[1];
+
+    assertEquals("Not 'OK' button", MessageManager.getString("action.ok"),
+            ((JButton) jb1).getText());
+    assertEquals("Wrong button font", JvSwingUtils.getLabelFont(),
+            jb1.getFont());
+
+    assertEquals("Not 'Cancel' button",
+            MessageManager.getString("action.cancel"),
+            ((JButton) jb2).getText());
+    assertEquals("Wrong button font", JvSwingUtils.getLabelFont(),
+            jb2.getFont());
+  }
+
+  /**
+   * Test 'Apply to' has 3 radio buttons enabled, 'Selected Sequences' selected,
+   * when there is a current selection group.
+   */
+  @Test
+  public void testBuildApplyToOptionsPanel_withSelectionGroup()
+  {
+    selectSequences(0, 2, 3);
+    testee = new AnnotationChooser(parentPanel);
+
+    JPanel jp = testee.buildApplyToOptionsPanel();
+    Component[] comps = jp.getComponents();
+    assertEquals("Not 3 radio buttons", 3, comps.length);
+
+    final Checkbox cb1 = (Checkbox) comps[0];
+    final Checkbox cb2 = (Checkbox) comps[1];
+    final Checkbox cb3 = (Checkbox) comps[2];
+
+    assertTrue("Not enabled", cb1.isEnabled());
+    assertTrue("Not enabled", cb2.isEnabled());
+    assertTrue("Not enabled", cb3.isEnabled());
+    assertEquals("Option not selected", cb2, cb2.getCheckboxGroup()
+            .getSelectedCheckbox());
+
+    // check state variables match checkbox selection
+    assertTrue(testee.isApplyToSelectedSequences());
+    assertFalse(testee.isApplyToUnselectedSequences());
+  }
+
+  /**
+   * Add a sequence group to the alignment with the specified sequences (base 0)
+   * in it
+   * 
+   * @param i
+   * @param more
+   */
+  private void selectSequences(int... selected)
+  {
+    SequenceI[] seqs = parentPanel.getAlignment().getSequencesArray();
+    SequenceGroup sg = new SequenceGroup();
+    for (int i : selected)
+    {
+      sg.addSequence(seqs[i], false);
+    }
+    parentPanel.av.setSelectionGroup(sg);
+  }
+
+  /**
+   * Test 'Apply to' has 1 radio button enabled, 'All Sequences' selected, when
+   * there is no current selection group.
+   */
+  @Test
+  public void testBuildApplyToOptionsPanel_noSelectionGroup()
+  {
+    testee = new AnnotationChooser(parentPanel);
+    JPanel jp = testee.buildApplyToOptionsPanel();
+    verifyApplyToOptionsPanel_noSelectionGroup(jp);
+  }
+
+  protected void verifyApplyToOptionsPanel_noSelectionGroup(JPanel jp)
+  {
+    assertTrue("Wrong layout", jp.getLayout() instanceof FlowLayout);
+    Component[] comps = jp.getComponents();
+    assertEquals("Not 3 radio buttons", 3, comps.length);
+
+    final Checkbox cb1 = (Checkbox) comps[0];
+    final Checkbox cb2 = (Checkbox) comps[1];
+    final Checkbox cb3 = (Checkbox) comps[2];
+
+    assertTrue("Not enabled", cb1.isEnabled());
+    assertFalse("Enabled", cb2.isEnabled());
+    assertFalse("Enabled", cb3.isEnabled());
+    assertEquals("Not selected", cb1, cb1.getCheckboxGroup()
+            .getSelectedCheckbox());
+
+    // check state variables match checkbox selection
+    assertTrue(testee.isApplyToSelectedSequences());
+    assertTrue(testee.isApplyToUnselectedSequences());
+
+    assertEquals("Wrong text",
+            MessageManager.getString("label.all_sequences"), cb1.getLabel());
+    assertEquals("Wrong text",
+            MessageManager.getString("label.selected_sequences"),
+            cb2.getLabel());
+    assertEquals("Wrong text",
+            MessageManager.getString("label.except_selected_sequences"),
+            cb3.getLabel());
+  }
+
+  /**
+   * Test Show and Hide radio buttons created, with Hide initially selected.
+   */
+  @Test
+  public void testBuildShowHidePanel()
+  {
+    testee = new AnnotationChooser(parentPanel);
+    JPanel jp = testee.buildShowHidePanel();
+    verifyShowHidePanel(jp);
+
+  }
+
+  protected void verifyShowHidePanel(JPanel jp)
+  {
+    assertTrue("Wrong layout", jp.getLayout() instanceof FlowLayout);
+    Component[] comps = jp.getComponents();
+    assertEquals("Not 2 radio buttons", 2, comps.length);
+
+    final Checkbox cb1 = (Checkbox) comps[0];
+    final Checkbox cb2 = (Checkbox) comps[1];
+
+    assertTrue("Show not enabled", cb1.isEnabled());
+    assertTrue("Hide not enabled", cb2.isEnabled());
+
+    // Hide (button 2) selected; note this may change to none (null)
+    assertEquals("Not selected", cb2, cb2.getCheckboxGroup()
+            .getSelectedCheckbox());
+
+    assertTrue("Show is flagged", !testee.isShowSelected());
+
+    assertEquals("Wrong text",
+            MessageManager.getString("label.show_selected_annotations"),
+            cb1.getLabel());
+    assertEquals("Wrong text",
+            MessageManager.getString("label.hide_selected_annotations"),
+            cb2.getLabel());
+  }
+
+  /**
+   * Test construction of panel containing two sub-panels
+   */
+  @Test
+  public void testBuildShowHideOptionsPanel()
+  {
+    testee = new AnnotationChooser(parentPanel);
+    JPanel jp = testee.buildShowHideOptionsPanel();
+    assertTrue("Wrong layout", jp.getLayout() instanceof BorderLayout);
+    Component[] comps = jp.getComponents();
+    assertEquals("Not 2 sub-panels", 2, comps.length);
+
+    verifyShowHidePanel((JPanel) comps[0]);
+    verifyApplyToOptionsPanel_noSelectionGroup((JPanel) comps[1]);
+  }
+
+  /**
+   * Test that annotation types are (uniquely) identified.
+   * 
+   */
+  @Test
+  public void testGetAnnotationTypes()
+  {
+    selectSequences(1);
+    testee = new AnnotationChooser(parentPanel);
+    // selection group should make no difference to the result
+    // as all annotation types for the alignment are considered
+
+    List<String> types = AnnotationChooser.getAnnotationTypes(
+            parentPanel.getAlignment(), true);
+    assertEquals("Not two annotation types", 2, types.size());
+    assertTrue("IUPRED missing", types.contains("IUPRED"));
+    assertTrue("JMol missing", types.contains("JMol"));
+
+    types = AnnotationChooser.getAnnotationTypes(
+            parentPanel.getAlignment(), false);
+    assertEquals("Not six annotation types", 6, types.size());
+    assertTrue("IUPRED missing", types.contains("IUPRED"));
+    assertTrue("JMol missing", types.contains("JMol"));
+    assertTrue("Beauty missing", types.contains("Beauty"));
+    // These are added by viewmodel.AlignViewport.initAutoAnnotation():
+    assertTrue("Consensus missing", types.contains("Consensus"));
+    assertTrue("Quality missing", types.contains("Quality"));
+    assertTrue("Conservation missing", types.contains("Conservation"));
+  }
+
+  /**
+   * Test result of selecting an annotation type, with 'Hide for all sequences'.
+   * 
+   * We expect all annotations of that type to be set hidden. Other annotations
+   * should be left visible.
+   */
+  @Test
+  public void testSelectType_hideForAll()
+  {
+    selectSequences(1, 2);
+    testee = new AnnotationChooser(parentPanel);
+    final Checkbox hideCheckbox = (Checkbox) getComponent(testee, 1, 0, 1);
+    setSelected(hideCheckbox, true);
+
+    final Checkbox allSequencesCheckbox = (Checkbox) getComponent(testee,
+            1, 1, 0);
+    setSelected(allSequencesCheckbox, true);
+
+    AlignmentAnnotation[] anns = parentPanel.getAlignment()
+            .getAlignmentAnnotation();
+
+    assertTrue(anns[5].visible); // JMol for seq3
+    assertTrue(anns[7].visible); // JMol for seq1
+
+    setSelected(getTypeCheckbox("JMol"), true);
+    assertTrue(anns[0].visible); // Conservation
+    assertTrue(anns[1].visible); // Quality
+    assertTrue(anns[2].visible); // Consensus
+    assertTrue(anns[3].visible); // IUPred for seq0
+    assertTrue(anns[4].visible); // Beauty
+    assertFalse(anns[5].visible); // JMol for seq3 - not selected but hidden
+    assertTrue(anns[6].visible); // IUPRED for seq2
+    assertFalse(anns[7].visible); // JMol for seq1 - selected and hidden
+  }
+
+  /**
+   * Test result of selecting an annotation type, with 'Hide for selected
+   * sequences'.
+   * 
+   * We expect the annotations of that type, linked to the sequence group, to be
+   * set hidden. Other annotations should be left visible.
+   */
+  @Test
+  public void testSelectType_hideForSelected()
+  {
+    selectSequences(1, 2);
+    testee = new AnnotationChooser(parentPanel);
+    final Checkbox hideCheckbox = (Checkbox) getComponent(testee, 1, 0, 1);
+    setSelected(hideCheckbox, true);
+
+    /*
+     * Don't set the 'selected sequences' radio button since this would trigger
+     * an update, including unselected sequences / annotation types
+     */
+    // setSelected(getSelectedSequencesCheckbox());
+
+    AlignmentAnnotation[] anns = parentPanel.getAlignment()
+            .getAlignmentAnnotation();
+
+    assertTrue(anns[7].visible); // JMol for seq1
+
+    setSelected(getTypeCheckbox("JMol"), true);
+    assertTrue(anns[0].visible); // Conservation
+    assertTrue(anns[1].visible); // Quality
+    assertTrue(anns[2].visible); // Consensus
+    assertTrue(anns[3].visible); // IUPred for seq0
+    assertTrue(anns[4].visible); // Beauty
+    assertTrue(anns[5].visible); // JMol for seq3 not in selection group
+    assertTrue(anns[6].visible); // IUPRED for seq2
+    assertFalse(anns[7].visible); // JMol for seq1 in selection group
+  }
+
+  /**
+   * Test result of deselecting an annotation type, with 'Hide for all
+   * sequences'.
+   * 
+   * We expect all annotations of that type to be set visible. Other annotations
+   * should be left unchanged.
+   */
+  @Test
+  public void testDeselectType_hideForAll()
   {
-    FileLoader fl = new jalview.io.FileLoader(false);
-    af = fl.LoadFileWaitTillLoaded(TEST_FILE,
-            AppletFormatAdapter.FILE);
+    selectSequences(1, 2);
+    testee = new AnnotationChooser(parentPanel);
+
+    final Checkbox hideCheckbox = (Checkbox) getComponent(testee, 1, 0, 1);
+    setSelected(hideCheckbox, true);
+
+    final Checkbox allSequencesCheckbox = (Checkbox) getComponent(testee,
+            1, 1, 0);
+    setSelected(allSequencesCheckbox, true);
 
+    AlignmentAnnotation[] anns = parentPanel.getAlignment()
+            .getAlignmentAnnotation();
+
+    final Checkbox typeCheckbox = getTypeCheckbox("JMol");
+
+    // select JMol - all hidden
+    setSelected(typeCheckbox, true);
+    assertFalse(anns[5].visible); // JMol for seq3
+    assertFalse(anns[7].visible); // JMol for seq1
+
+    // deselect JMol - all unhidden
+    setSelected(typeCheckbox, false);
+    assertTrue(anns[0].visible); // Conservation
+    assertTrue(anns[1].visible); // Quality
+    assertTrue(anns[2].visible); // Consensus
+    assertTrue(anns[3].visible); // IUPred for seq0
+    assertTrue(anns[4].visible); // Beauty
+    assertTrue(anns[5].visible); // JMol for seq3
+    assertTrue(anns[6].visible); // IUPRED for seq2
+    assertTrue(anns[7].visible); // JMol for seq1
   }
 
+  /**
+   * Test result of deselecting an annotation type, with 'Hide for selected
+   * sequences'.
+   * 
+   * We expect the annotations of that type, linked to the sequence group, to be
+   * set visible. Other annotations should be left unchanged.
+   */
   @Test
-  public void testAlignmentLoader() throws Exception
+  public void testDeselectType_hideForSelected()
   {
+    selectSequences(1, 2);
+    testee = new AnnotationChooser(parentPanel);
+    final Checkbox hideCheckbox = (Checkbox) getComponent(testee, 1, 0, 1);
+    setSelected(hideCheckbox, true);
+
+    /*
+     * Don't set the 'selected sequences' radio button since this would trigger
+     * an update, including unselected sequences / annotation types
+     */
+    // setSelected(getSelectedSequencesCheckbox());
 
-    AlignmentPanel parentPanel = new AlignmentPanel(af, af.getViewport());
+    setSelected(getTypeCheckbox("JMol"), true);
+    setSelected(getTypeCheckbox("JMol"), false);
+
+    AlignmentAnnotation[] anns = parentPanel.getAlignment()
+            .getAlignmentAnnotation();
+    assertTrue(anns[0].visible); // Conservation
+    assertTrue(anns[1].visible); // Quality
+    assertTrue(anns[2].visible); // Consensus
+    assertTrue(anns[3].visible); // IUPred for seq0
+    assertTrue(anns[4].visible); // Beauty
+    assertTrue(anns[5].visible); // JMol for seq3 not in selection group
+    assertTrue(anns[6].visible); // IUPRED for seq2
+    assertTrue(anns[7].visible); // JMol for seq1 in selection group
+  }
+
+  /**
+   * Test result of selecting an annotation type, with 'Show for all sequences'.
+   * 
+   * We expect all annotations of that type to be set visible. Other annotations
+   * should be left unchanged
+   */
+  @Test
+  public void testSelectType_showForAll()
+  {
+    selectSequences(1, 2);
     testee = new AnnotationChooser(parentPanel);
+    final Checkbox showCheckbox = (Checkbox) getComponent(testee, 1, 0, 0);
+    final Checkbox hideCheckbox = (Checkbox) getComponent(testee, 1, 0, 1);
+
+    final Checkbox allSequencesCheckbox = (Checkbox) getComponent(testee,
+            1, 1, 0);
+
+    AlignmentAnnotation[] anns = parentPanel.getAlignment()
+            .getAlignmentAnnotation();
+
+    // hide all JMol annotations
+    setSelected(allSequencesCheckbox, true);
+    setSelected(hideCheckbox, true);
+    setSelected(getTypeCheckbox("JMol"), true);
+    assertFalse(anns[5].visible); // JMol for seq3
+    assertFalse(anns[7].visible); // JMol for seq1
+    // ...now show them...
+    setSelected(showCheckbox, true);
+    assertTrue(anns[0].visible); // Conservation
+    assertTrue(anns[1].visible); // Quality
+    assertTrue(anns[2].visible); // Consensus
+    assertTrue(anns[3].visible); // IUPred for seq0
+    assertTrue(anns[4].visible); // Beauty
+    assertTrue(anns[5].visible); // JMol for seq3
+    assertTrue(anns[6].visible); // IUPRED for seq2
+    assertTrue(anns[7].visible); // JMol for seq1
+  }
+
+  /**
+   * Test result of selecting an annotation type, with 'Show for selected
+   * sequences'.
+   * 
+   * We expect all annotations of that type, linked to the sequence group, to be
+   * set visible. Other annotations should be left unchanged
+   */
+  @Test
+  public void testSelectType_showForSelected()
+  {
+    selectSequences(1, 2);
+    testee = new AnnotationChooser(parentPanel);
+    final Checkbox showCheckbox = (Checkbox) getComponent(testee, 1, 0, 0);
+    final Checkbox hideCheckbox = (Checkbox) getComponent(testee, 1, 0, 1);
+
+    final Checkbox selectedSequencesCheckbox = (Checkbox) getComponent(
+            testee, 1, 1, 1);
+
+    AlignmentAnnotation[] anns = parentPanel.getAlignment()
+            .getAlignmentAnnotation();
+
+    // hide all JMol annotations in the selection region (== annotation 7)
+    setSelected(selectedSequencesCheckbox, true);
+    setSelected(hideCheckbox, true);
+    setSelected(getTypeCheckbox("JMol"), true);
+    assertTrue(anns[5].visible); // JMol for seq3
+    assertFalse(anns[7].visible); // JMol for seq1
+    // ...now show them...
+    setSelected(showCheckbox, true);
+
+    assertTrue(anns[0].visible); // Conservation
+    assertTrue(anns[1].visible); // Quality
+    assertTrue(anns[2].visible); // Consensus
+    assertTrue(anns[3].visible); // IUPred for seq0
+    assertTrue(anns[4].visible); // Beauty
+    assertTrue(anns[5].visible); // JMol for seq3
+    assertTrue(anns[6].visible); // IUPRED for seq2
+    assertTrue(anns[7].visible); // JMol for seq1
   }
 
+  /**
+   * Test result of deselecting an annotation type, with 'Show for all
+   * sequences'.
+   * 
+   * We expect all annotations of that type to be set hidden. Other annotations
+   * should be left unchanged.
+   */
   @Test
-  public void testBuildFrame()
+  public void testDeselectType_showForAll()
   {
+    selectSequences(1, 2);
+    testee = new AnnotationChooser(parentPanel);
+
+    final Checkbox showCheckbox = (Checkbox) getComponent(testee, 1, 0, 0);
+    setSelected(showCheckbox, true);
+
+    final Checkbox allSequencesCheckbox = (Checkbox) getComponent(testee,
+            1, 1, 0);
+    setSelected(allSequencesCheckbox, true);
+
+    AlignmentAnnotation[] anns = parentPanel.getAlignment()
+            .getAlignmentAnnotation();
+
+    final Checkbox typeCheckbox = getTypeCheckbox("JMol");
+    // select JMol - all shown
+    setSelected(typeCheckbox, true);
+    assertTrue(anns[5].visible); // JMol for seq3
+    assertTrue(anns[7].visible); // JMol for seq1
+
+    // deselect JMol - all hidden
+    setSelected(typeCheckbox, false);
+    assertTrue(anns[0].visible); // Conservation
+    assertTrue(anns[1].visible); // Quality
+    assertTrue(anns[2].visible); // Consensus
+    assertTrue(anns[3].visible); // IUPred for seq0
+    assertTrue(anns[4].visible); // Beauty
+    assertFalse(anns[5].visible); // JMol for seq3
+    assertTrue(anns[6].visible); // IUPRED for seq2
+    assertFalse(anns[7].visible); // JMol for seq1
+  }
+
+  /**
+   * Test result of deselecting an annotation type, with 'Show for selected
+   * sequences'.
+   * 
+   * We expect the annotations of that type, linked to the sequence group, to be
+   * set hidden. Other annotations should be left unchanged.
+   */
+  @Test
+  public void testDeselectType_showForSelected()
+  {
+    selectSequences(1, 2);
+    testee = new AnnotationChooser(parentPanel);
+    final Checkbox showCheckbox = (Checkbox) getComponent(testee, 1, 0, 0);
+    setSelected(showCheckbox, true);
+
+    /*
+     * Don't set the 'selected sequences' radio button since this would trigger
+     * an update, including unselected sequences / annotation types
+     */
+    // setSelected(getSelectedSequencesCheckbox());
+
+    AlignmentAnnotation[] anns = parentPanel.getAlignment()
+            .getAlignmentAnnotation();
+
+    // select JMol - should remain visible
+    setSelected(getTypeCheckbox("JMol"), true);
+    assertTrue(anns[5].visible); // JMol for seq3
+    assertTrue(anns[7].visible); // JMol for seq1
+
+    // deselect JMol - should be hidden for selected sequences only
+    setSelected(getTypeCheckbox("JMol"), false);
+    assertTrue(anns[0].visible); // Conservation
+    assertTrue(anns[1].visible); // Quality
+    assertTrue(anns[2].visible); // Consensus
+    assertTrue(anns[3].visible); // IUPred for seq0
+    assertTrue(anns[4].visible); // Beauty
+    assertTrue(anns[5].visible); // JMol for seq3 not in selection group
+    assertTrue(anns[6].visible); // IUPRED for seq2
+    assertFalse(anns[7].visible); // JMol for seq1 in selection group
+  }
+
+  /**
+   * Helper method to drill down to a sub-component in a Container hierarchy.
+   * 
+   * @param cont
+   * @param i
+   * @param j
+   * @param k
+   * @return
+   */
+  public static Component getComponent(Container cont, int... positions)
+  {
+    Component comp = cont;
+    for (int i : positions)
+    {
+      comp = ((Container) comp).getComponent(i);
+    }
+    return comp;
+  }
+
+  /**
+   * Helper method to set or unset a checkbox and fire its action listener.
+   * 
+   * @param cb
+   * @param select
+   */
+  protected void setSelected(Checkbox cb, boolean select)
+  {
+    // TODO refactor to a test utility class
+    cb.setState(select);
+    // have to manually fire the action listener
+    cb.getItemListeners()[0].itemStateChanged(new ItemEvent(cb,
+            ItemEvent.ITEM_STATE_CHANGED, cb, select ? ItemEvent.SELECTED
+                    : ItemEvent.DESELECTED));
+  }
+
+  /**
+   * Helper method to drill down to the 'Annotation type' checkbox with given
+   * label.
+   * 
+   * @return
+   */
+  private Checkbox getTypeCheckbox(String forLabel)
+  {
+    Component[] cbs = ((JPanel) testee.getComponent(0)).getComponents();
+    for (Component comp : cbs)
+    {
+      final Checkbox cb = (Checkbox) comp;
+      if (cb.getLabel().equals(forLabel))
+      {
+        return cb;
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Test isInActionScope for the case where the scope is selected sequences.
+   * Test cases include sequences in the selection group, and others not in the
+   * group.
+   */
+  @Test
+  public void testIsInActionScope_selectedScope()
+  {
+    // sequences 1 and 2 have annotations 4 and 3 respectively
+    selectSequences(1, 2);
+    testee = new AnnotationChooser(parentPanel);
+
+    final Checkbox selectedSequencesCheckbox = (Checkbox) getComponent(
+            testee, 1, 1, 1);
+    setSelected(selectedSequencesCheckbox, true);
+
+    AlignmentAnnotation[] anns = parentPanel.getAlignment()
+            .getAlignmentAnnotation();
+    // remember 3 annotations to skip (Conservation/Quality/Consensus)
+    assertFalse(testee.isInActionScope(anns[3]));
+    assertFalse(testee.isInActionScope(anns[4]));
+    assertFalse(testee.isInActionScope(anns[5]));
+    assertTrue(testee.isInActionScope(anns[6]));
+    assertTrue(testee.isInActionScope(anns[7]));
+  }
+
+  /**
+   * Test isInActionScope for the case where the scope is unselected sequences.
+   * Test cases include sequences in the selection group, and others not in the
+   * group.
+   */
+  @Test
+  public void testIsInActionScope_unselectedScope()
+  {
+    // sequences 1 and 2 have annotations 4 and 3 respectively
+    selectSequences(1, 2);
+    testee = new AnnotationChooser(parentPanel);
+
+    final Checkbox unselectedSequencesCheckbox = (Checkbox) getComponent(
+            testee, 1, 1, 2);
+    setSelected(unselectedSequencesCheckbox, true);
+
+    AlignmentAnnotation[] anns = parentPanel.getAlignment()
+            .getAlignmentAnnotation();
+    // remember 3 annotations to skip (Conservation/Quality/Consensus)
+    assertTrue(testee.isInActionScope(anns[3]));
+    assertTrue(testee.isInActionScope(anns[4]));
+    assertTrue(testee.isInActionScope(anns[5]));
+    assertFalse(testee.isInActionScope(anns[6]));
+    assertFalse(testee.isInActionScope(anns[7]));
+  }
+
+  /**
+   * Test that the reset method restores previous visibility flags.
+   */
+  @Test
+  public void testResetOriginalState()
+  {
+    testee = new AnnotationChooser(parentPanel);
+
+    AlignmentAnnotation[] anns = parentPanel.getAlignment()
+            .getAlignmentAnnotation();
+    // all start visible
+    for (int i = 0; i < anns.length; i++)
+    {
+      assertTrue(i + "'th sequence not visible", anns[i].visible);
+    }
+
+    final Checkbox hideCheckbox = (Checkbox) getComponent(testee, 1, 0, 1);
+    setSelected(hideCheckbox, true);
+
+    final Checkbox allSequencesCheckbox = (Checkbox) getComponent(testee,
+            1, 1, 0);
+    setSelected(allSequencesCheckbox, true);
+
+    setSelected(getTypeCheckbox("JMol"), true);
+    setSelected(getTypeCheckbox("IUPRED"), true);
+
+    assertTrue(anns[0].visible); // Conservation
+    assertTrue(anns[1].visible); // Quality
+    assertTrue(anns[2].visible); // Consensus
+    assertFalse(anns[3].visible); // IUPRED
+    assertTrue(anns[4].visible); // Beauty (not seq-related)
+    assertFalse(anns[5].visible); // JMol
+    assertFalse(anns[6].visible); // IUPRED
+    assertFalse(anns[7].visible); // JMol
 
+    // reset - should all be visible
+    testee.resetOriginalState();
+    for (int i = 0; i < anns.length; i++)
+    {
+      assertTrue(i + "'th sequence not visible", anns[i].visible);
+    }
   }
 }