JAL-1264 refactored show/hide annotation types including groups
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Fri, 3 Oct 2014 11:04:45 +0000 (12:04 +0100)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Fri, 3 Oct 2014 11:04:45 +0000 (12:04 +0100)
src/jalview/gui/PopupMenu.java

index cd60e47..260bcc5 100644 (file)
@@ -34,6 +34,7 @@ import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.io.FormatAdapter;
 import jalview.io.SequenceAnnotationReport;
+import jalview.renderer.AnnotationRenderer;
 import jalview.schemes.AnnotationColourGradient;
 import jalview.schemes.Blosum62ColourScheme;
 import jalview.schemes.BuriedColourScheme;
@@ -58,8 +59,13 @@ import java.awt.Color;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Collection;
 import java.util.Hashtable;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Vector;
 
 import javax.swing.ButtonGroup;
@@ -79,6 +85,10 @@ import javax.swing.JRadioButtonMenuItem;
  */
 public class PopupMenu extends JPopupMenu
 {
+  private static final String ALL_ANNOTATIONS = "All";
+
+  private static final String COMMA = ",";
+
   JMenu groupMenu = new JMenu();
 
   JMenuItem groupName = new JMenuItem();
@@ -266,7 +276,10 @@ public class PopupMenu extends JPopupMenu
       outputMenu.add(item);
     }
 
-    buildAnnotationTypesMenu();
+    /*
+     * Build menus for annotation types that may be shown or hidden.
+     */
+    buildAnnotationTypesMenus();
 
     try
     {
@@ -295,7 +308,6 @@ public class PopupMenu extends JPopupMenu
           menuItem.setText(pdb.getId());
           menuItem.addActionListener(new java.awt.event.ActionListener()
           {
-            @Override
             public void actionPerformed(ActionEvent e)
             {
               // TODO re JAL-860: optionally open dialog or provide a menu entry
@@ -307,7 +319,6 @@ public class PopupMenu extends JPopupMenu
                               ap.av.collateForPDB(new PDBEntry[]
                               { pdb })[0], null, ap);
             }
-
           });
           viewStructureMenu.add(menuItem);
 
@@ -570,8 +581,7 @@ public class PopupMenu extends JPopupMenu
       SequenceI sqass = null;
       for (SequenceI sq : ap.av.getSequenceSelection())
       {
-        Vector<PDBEntry> pes = sq.getDatasetSequence()
-                .getPDBId();
+        Vector<PDBEntry> pes = sq.getDatasetSequence().getPDBId();
         if (pes != null && pes.size() > 0)
         {
           reppdb.put(pes.get(0).getId(), pes.get(0));
@@ -786,67 +796,234 @@ public class PopupMenu extends JPopupMenu
   }
 
   /**
-   * Find which sequence-specific annotation types are associated with the
-   * current selection, and add these as menu items (for show / hide annotation
-   * types).
+   * Add annotation types to a 'Show annotations' or 'Hide annotations' menu.
+   * "All" is added first, followed by a separator. Then add any annotation
+   * types associated with the current selection. The second parameter controls
+   * whether we include only currently visible annotation types (for the Hide
+   * menu), or only currently hidden annotation types (for the Show menu).
+   * <p>
+   * Some annotation rows are always rendered together - these can be identified
+   * by a common graphGroup property > -1. Only one of each group will be marked
+   * as visible (to avoid duplication of the display). For such groups we add a
+   * composite type name, e.g.
+   * <p>
+   * IUPredWS (Long), IUPredWS (Short)
    */
-  protected void buildAnnotationTypesMenu()
+  protected void buildAnnotationTypesMenus()
   {
-    List<String> found = new ArrayList<String>();
-    for (AlignmentAnnotation aa : ap.getAlignment()
-            .getAlignmentAnnotation())
+    showAnnotationsMenu.removeAll();
+    hideAnnotationsMenu.removeAll();
+    final List<String> all = Arrays.asList(ALL_ANNOTATIONS);
+    addAnnotationTypeToShowHide(showAnnotationsMenu,
+            all, true,
+ true);
+    addAnnotationTypeToShowHide(hideAnnotationsMenu, all, true,
+            false);
+    showAnnotationsMenu.addSeparator();
+    hideAnnotationsMenu.addSeparator();
+
+    final AlignmentAnnotation[] annotations = ap.getAlignment()
+            .getAlignmentAnnotation();
+    BitSet visibleGraphGroups = PopupMenu
+            .getVisibleLineGraphGroups(annotations);
+
+    List<List<String>> shownTypes = new ArrayList<List<String>>();
+    List<List<String>> hiddenTypes = new ArrayList<List<String>>();
+    PopupMenu.getAnnotationTypesForShowHide(shownTypes, hiddenTypes,
+            visibleGraphGroups, annotations, ap.av.getSelectionGroup());
+
+    for (List<String> types : hiddenTypes)
+    {
+      addAnnotationTypeToShowHide(showAnnotationsMenu, types, false, true);
+    }
+
+    for (List<String> types : shownTypes)
+    {
+      addAnnotationTypeToShowHide(hideAnnotationsMenu, types, false, false);
+    }
+  }
+
+  /**
+   * Helper method to populate lists of annotation types for the Show/Hide
+   * Annotations menus. If sequenceGroup is not null, this is restricted to
+   * annotations which are associated with sequences in the selection group.
+   * <p/>
+   * If an annotation row is currently visible, its type (label) is added (once
+   * only per type), to the shownTypes list. If it is currently hidden, it is
+   * added to the hiddenTypesList.
+   * <p/>
+   * For rows that belong to a line graph group, so are always rendered
+   * together:
+   * <ul>
+   * <li>Treat all rows in the group as visible, if at least one of them is</li>
+   * <li>Build a comma-separated label with all the types that belong to the
+   * group</li>
+   * </ul>
+   * 
+   * @param shownTypes
+   * @param hiddenTypes
+   * @param visibleGraphGroups
+   * @param annotations
+   * @param sequenceGroup
+   */
+  public static void getAnnotationTypesForShowHide(
+          List<List<String>> shownTypes, List<List<String>> hiddenTypes,
+          BitSet visibleGraphGroups,
+          AlignmentAnnotation[] annotations, SequenceGroup sequenceGroup)
+  {
+    // lookup table, key = graph group, value = list of types in the group
+    Map<Integer, List<String>> groupLabels = new LinkedHashMap<Integer, List<String>>();
+
+    List<String> addedToShown = new ArrayList<String>();
+    List<String> addedToHidden = new ArrayList<String>();
+
+    for (AlignmentAnnotation aa : annotations)
     {
-      if (aa.sequenceRef != null)
+
+      if (sequenceGroup == null
+              || (aa.sequenceRef != null && sequenceGroup.getSequences()
+                      .contains(aa.sequenceRef)))
       {
-        if (ap.av.getSelectionGroup().getSequences()
-                .contains(aa.sequenceRef))
+        /*
+         * Build a 'composite label' for types in line graph groups.
+         */
+        final List<String> labelAsList = new ArrayList<String>();
+        labelAsList.add(aa.label);
+        if (aa.graph == AlignmentAnnotation.LINE_GRAPH
+                && aa.graphGroup > -1)
         {
-          final String label = aa.label;
-          if (!found.contains(label))
+          if (groupLabels.containsKey(aa.graphGroup))
           {
-            found.add(label);
-            final JMenuItem showitem = new JMenuItem(label);
-            showitem.addActionListener(new java.awt.event.ActionListener()
-            {
-              @Override
-              public void actionPerformed(ActionEvent e)
-              {
-                showHideAnnotation_actionPerformed(label, true);
-              }
-            });
-            showAnnotationsMenu.add(showitem);
-            final JMenuItem hideitem = new JMenuItem(label);
-            hideitem.addActionListener(new java.awt.event.ActionListener()
+            if (!groupLabels.get(aa.graphGroup).contains(aa.label))
             {
-              @Override
-              public void actionPerformed(ActionEvent e)
-              {
-                showHideAnnotation_actionPerformed(label, false);
-              }
-            });
-            hideAnnotationsMenu.add(hideitem);
+              groupLabels.get(aa.graphGroup).add(aa.label);
+            }
+          }
+          else
+          {
+            groupLabels.put(aa.graphGroup, labelAsList);
           }
         }
+        else if (aa.visible && !addedToShown.contains(aa.label))
+        {
+          shownTypes.add(labelAsList);
+          addedToShown.add(aa.label);
+        }
+        else
+        {
+          if (!aa.visible && !addedToHidden.contains(aa.label))
+          {
+            hiddenTypes.add(labelAsList);
+            addedToHidden.add(aa.label);
+          }
+        }
+      }
+    }
+    /*
+     * finally add the 'composite group labels' to the appropriate lists,
+     * depending on whether the group is identified as visible or hidden
+     */
+    for (int group : groupLabels.keySet())
+    {
+      final List<String> groupLabel = groupLabels.get(group);
+      if (visibleGraphGroups.get(group))
+      {
+        if (!shownTypes.contains(groupLabel))
+        {
+          shownTypes.add(groupLabel);
+        }
+      }
+      else if (!hiddenTypes.contains(groupLabel))
+      {
+        hiddenTypes.add(groupLabel);
       }
     }
   }
 
   /**
-   * Action on selecting an annotation type to show or hide for the selection.
+   * Returns a BitSet (possibly empty) of those graphGroups for line graph
+   * annotations, which have at least one member annotation row marked visible.
+   * The logic is that only one row in the group is marked visible, but when it
+   * is drawn, so are all the other rows in the same group.
+   * <p/>
+   * This lookup set allows us to check whether rows marked not visible are in
+   * fact shown.
    * 
-   * @param type
+   * @see AnnotationRenderer#drawComponent
+   * @param annotations
+   * @return
+   */
+  public static BitSet getVisibleLineGraphGroups(
+          AlignmentAnnotation[] annotations)
+  {
+    // todo move to a utility class
+    BitSet result = new BitSet();
+    for (AlignmentAnnotation ann : annotations)
+    {
+      if (ann.graph == AlignmentAnnotation.LINE_GRAPH && ann.visible)
+      {
+        int gg = ann.graphGroup;
+        if (gg > -1)
+        {
+          result.set(gg);
+        }
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Add one annotation type to the 'Show Annotations' or 'Hide Annotations'
+   * menus.
+   * 
+   * @param showOrHideMenu
+   *          the menu to add to
+   * @param types
+   *          the label to add
+   * @param allTypes
+   *          if true this is a special label meaning 'All'
+   * @param actionIsShow
+   *          if true, the select menu item action is to show the annotation
+   *          type, else hide
+   */
+  protected void addAnnotationTypeToShowHide(JMenu showOrHideMenu,
+          final Collection<String> types, final boolean allTypes,
+          final boolean actionIsShow)
+  {
+    String label = types.toString(); // [a, b, c]
+    label = label.substring(1, label.length() - 1);
+    final JMenuItem item = new JMenuItem(label);
+    item.addActionListener(new java.awt.event.ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        showHideAnnotation_actionPerformed(types, allTypes, actionIsShow);
+      }
+    });
+    showOrHideMenu.add(item);
+  }
+
+  /**
+   * Action on selecting a list of annotation type (or the 'all types' values)
+   * to show or hide for the selection.
+   * 
+   * @param types
+   * @param anyType
    * @param doShow
    */
-  protected void showHideAnnotation_actionPerformed(String type,
-          boolean doShow)
+  protected void showHideAnnotation_actionPerformed(
+          Collection<String> types,
+          boolean anyType, boolean doShow)
   {
     for (AlignmentAnnotation aa : ap.getAlignment()
             .getAlignmentAnnotation())
     {
-      if (aa.sequenceRef != null && type.equals(aa.label))
+      if (anyType || types.contains(aa.label))
       {
-        if (ap.av.getSelectionGroup().getSequences()
-                .contains(aa.sequenceRef))
+        if ((aa.sequenceRef != null)
+                && ap.av.getSelectionGroup().getSequences()
+                        .contains(aa.sequenceRef))
         {
           aa.visible = doShow;
         }
@@ -1125,8 +1302,7 @@ public class PopupMenu extends JPopupMenu
     });
     chooseAnnotations.setText(MessageManager
             .getString("label.choose_annotations") + "...");
-    chooseAnnotations
-            .addActionListener(new java.awt.event.ActionListener()
+    chooseAnnotations.addActionListener(new java.awt.event.ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -1390,7 +1566,7 @@ public class PopupMenu extends JPopupMenu
     add(groupMenu);
     add(sequenceMenu);
     this.add(structureMenu);
-    groupMenu.add(chooseAnnotations);
+    // groupMenu.add(chooseAnnotations);
     groupMenu.add(showAnnotationsMenu);
     groupMenu.add(hideAnnotationsMenu);
     groupMenu.add(editMenu);
@@ -1877,6 +2053,7 @@ public class PopupMenu extends JPopupMenu
     // todo correct way to guard against opening a duplicate panel?
     new AnnotationChooser(ap);
   }
+
   /**
    * DOCUMENT ME!
    * 
@@ -2266,7 +2443,8 @@ public class PopupMenu extends JPopupMenu
     // or we simply trust the user wants
     // wysiwig behaviour
 
-    cap.setText(new FormatAdapter().formatSequences(e.getActionCommand(), ap.av, true));
+    cap.setText(new FormatAdapter().formatSequences(e.getActionCommand(),
+            ap.av, true));
   }
 
   public void pdbFromFile_actionPerformed()