From bbd829dc088612ca1ad505c412455573289522fa Mon Sep 17 00:00:00 2001 From: gmungoc Date: Fri, 3 Oct 2014 12:04:45 +0100 Subject: [PATCH] JAL-1264 refactored show/hide annotation types including groups --- src/jalview/gui/PopupMenu.java | 274 +++++++++++++++++++++++++++++++++------- 1 file changed, 226 insertions(+), 48 deletions(-) diff --git a/src/jalview/gui/PopupMenu.java b/src/jalview/gui/PopupMenu.java index cd60e47..260bcc5 100644 --- a/src/jalview/gui/PopupMenu.java +++ b/src/jalview/gui/PopupMenu.java @@ -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 pes = sq.getDatasetSequence() - .getPDBId(); + Vector 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). + *

+ * 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. + *

+ * IUPredWS (Long), IUPredWS (Short) */ - protected void buildAnnotationTypesMenu() + protected void buildAnnotationTypesMenus() { - List found = new ArrayList(); - for (AlignmentAnnotation aa : ap.getAlignment() - .getAlignmentAnnotation()) + showAnnotationsMenu.removeAll(); + hideAnnotationsMenu.removeAll(); + final List 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> shownTypes = new ArrayList>(); + List> hiddenTypes = new ArrayList>(); + PopupMenu.getAnnotationTypesForShowHide(shownTypes, hiddenTypes, + visibleGraphGroups, annotations, ap.av.getSelectionGroup()); + + for (List types : hiddenTypes) + { + addAnnotationTypeToShowHide(showAnnotationsMenu, types, false, true); + } + + for (List 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. + *

+ * 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. + *

+ * For rows that belong to a line graph group, so are always rendered + * together: + *

    + *
  • Treat all rows in the group as visible, if at least one of them is
  • + *
  • Build a comma-separated label with all the types that belong to the + * group
  • + *
+ * + * @param shownTypes + * @param hiddenTypes + * @param visibleGraphGroups + * @param annotations + * @param sequenceGroup + */ + public static void getAnnotationTypesForShowHide( + List> shownTypes, List> hiddenTypes, + BitSet visibleGraphGroups, + AlignmentAnnotation[] annotations, SequenceGroup sequenceGroup) + { + // lookup table, key = graph group, value = list of types in the group + Map> groupLabels = new LinkedHashMap>(); + + List addedToShown = new ArrayList(); + List addedToHidden = new ArrayList(); + + 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 labelAsList = new ArrayList(); + 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 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. + *

+ * 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 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 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() -- 1.7.10.2