package jalview.gui;
import jalview.analysis.AAFrequency;
+import jalview.analysis.AlignmentAnnotationUtils;
import jalview.analysis.Conservation;
import jalview.commands.ChangeCaseCommand;
import jalview.commands.EditCommand;
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;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.BitSet;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.TreeMap;
import java.util.Vector;
import javax.swing.ButtonGroup;
JMenu hideAnnotationsMenu = new JMenu();
- JMenuItem addDatasequenceAnnotations = new JMenuItem();
+ JMenuItem addReferenceAnnotations = new JMenuItem();
JMenuItem sequenceFeature = new JMenuItem();
}
/*
- * Build menus for annotation types that may be shown or hidden.
+ * Build menus for annotation types that may be shown or hidden, and for
+ * 'reference annotations' that may be added to the alignment. The scope is
+ * annotations for the current selection group (if there is one), else the
+ * current sequence (if there is one), else not applicable (e.g. for popup
+ * menu within the sequence).
*/
- buildAnnotationTypesMenus();
+ final List<SequenceI> sequenceScope = getSequenceScope(seq);
+ if (!sequenceScope.isEmpty())
+ {
+ buildAnnotationTypesMenus(sequenceScope);
+ configureReferenceAnnotationsMenu(addReferenceAnnotations, sequenceScope);
+ }
try
{
menuItem = new JMenuItem();
menuItem.setText(pdb.getId());
- menuItem.addActionListener(new ActionListener()
+ menuItem.addActionListener(new ActionListener()
{
@Override
- public void actionPerformed(ActionEvent e) {
- // TODO re JAL-860: optionally open dialog or provide a menu entry
- // allowing user to open just one structure per sequence
- // new AppJmol(pdb, ap.av.collateForPDB(new PDBEntry[]
- // { pdb })[0], null, ap);
- new StructureViewer(ap.getStructureSelectionManager())
- .viewStructures(pdb,
- ap.av.collateForPDB(new PDBEntry[]
- { pdb })[0], null, ap);
+ public void actionPerformed(ActionEvent e)
+ {
+ // TODO re JAL-860: optionally open dialog or provide a menu entry
+ // allowing user to open just one structure per sequence
+ // new AppJmol(pdb, ap.av.collateForPDB(new PDBEntry[]
+ // { pdb })[0], null, ap);
+ new StructureViewer(ap.getStructureSelectionManager())
+ .viewStructures(pdb,
+ ap.av.collateForPDB(new PDBEntry[]
+ { pdb })[0], null, ap);
}
});
viewStructureMenu.add(menuItem);
* composite type name, e.g.
* <p>
* IUPredWS (Long), IUPredWS (Short)
+ *
+ * @param forSequences
*/
- protected void buildAnnotationTypesMenus()
+ protected void buildAnnotationTypesMenus(List<SequenceI> forSequences)
{
- final SequenceGroup selectionGroup = ap.av.getSelectionGroup();
- if (selectionGroup == null)
- {
- // this menu option is only for a selection
- return;
- }
-
showAnnotationsMenu.removeAll();
hideAnnotationsMenu.removeAll();
final List<String> all = Arrays.asList(ALL_ANNOTATIONS);
- addAnnotationTypeToShowHide(showAnnotationsMenu, "", all, true, true);
- addAnnotationTypeToShowHide(hideAnnotationsMenu, "", all, true, false);
+ addAnnotationTypeToShowHide(showAnnotationsMenu, forSequences, "", all,
+ true, true);
+ addAnnotationTypeToShowHide(hideAnnotationsMenu, forSequences, "", all,
+ true, false);
showAnnotationsMenu.addSeparator();
hideAnnotationsMenu.addSeparator();
final AlignmentAnnotation[] annotations = ap.getAlignment()
.getAlignmentAnnotation();
- BitSet visibleGraphGroups = PopupMenu
- .getVisibleLineGraphGroups(annotations);
/*
* Find shown/hidden annotations types, distinguished by source (calcId),
*/
Map<String, List<List<String>>> shownTypes = new HashMap<String, List<List<String>>>();
Map<String, List<List<String>>> hiddenTypes = new HashMap<String, List<List<String>>>();
- PopupMenu.getAnnotationTypesForShowHide(shownTypes, hiddenTypes,
- visibleGraphGroups, annotations, selectionGroup);
+ AlignmentAnnotationUtils.getShownHiddenTypes(shownTypes,
+ hiddenTypes,
+ AlignmentAnnotationUtils.asList(annotations),
+ forSequences);
for (String calcId : hiddenTypes.keySet())
{
for (List<String> type : hiddenTypes.get(calcId))
{
- addAnnotationTypeToShowHide(showAnnotationsMenu, calcId, type,
- false, true);
+ addAnnotationTypeToShowHide(showAnnotationsMenu, forSequences,
+ calcId, type, false, true);
}
}
+ // grey out 'show annotations' if none are hidden
+ showAnnotationsMenu.setEnabled(!hiddenTypes.isEmpty());
for (String calcId : shownTypes.keySet())
{
for (List<String> type : shownTypes.get(calcId))
{
- addAnnotationTypeToShowHide(hideAnnotationsMenu, calcId, type,
- false, false);
+ addAnnotationTypeToShowHide(hideAnnotationsMenu, forSequences,
+ calcId, type, false, false);
}
}
+ // grey out 'hide annotations' if none are shown
+ hideAnnotationsMenu.setEnabled(!shownTypes.isEmpty());
}
/**
- * 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>
+ * Returns a list of sequences - either the current selection group (if there
+ * is one), else the specified single sequence.
*
- * @param shownTypes
- * a map, keyed by calcId (annotation source), whose entries are the
- * lists of annotation types found for the calcId; each annotation
- * type in turn may be a list (in the case of grouped annotations)
- * @param hiddenTypes
- * a map, similar to shownTypes, but for hidden annotation types
- * @param visibleGraphGroups
- * a lookup keyed by graphGroup identifier
- * @param annotations
- * the annotations on the alignment to scan
- * @param sequenceGroup
- * the sequence group to restrict search to
+ * @param seq
+ * @return
*/
- public static void getAnnotationTypesForShowHide(
- Map<String, List<List<String>>> shownTypes,
- Map<String, List<List<String>>> hiddenTypes,
- BitSet visibleGraphGroups, AlignmentAnnotation[] annotations,
- SequenceGroup sequenceGroup)
+ protected List<SequenceI> getSequenceScope(SequenceI seq)
{
- /*
- * Build a lookup, by calcId (annotation source), of all annotation types in
- * each graph group.
- */
- Map<String, Map<Integer, List<String>>> groupLabels = new HashMap<String, Map<Integer, List<String>>>();
-
- // trackers for which calcId!label combinations we have dealt with
- List<String> addedToShown = new ArrayList<String>();
- List<String> addedToHidden = new ArrayList<String>();
-
- for (AlignmentAnnotation aa : annotations)
- {
-
- if (sequenceGroup == null
- || (aa.sequenceRef != null && sequenceGroup.getSequences()
- .contains(aa.sequenceRef)))
- {
- String calcId = aa.getCalcId();
-
- /*
- * Build a 'composite label' for types in line graph groups.
- */
- final List<String> labelAsList = new ArrayList<String>();
- final String displayLabel = aa.label;
- labelAsList.add(displayLabel);
- if (aa.graph == AlignmentAnnotation.LINE_GRAPH
- && aa.graphGroup > -1)
- {
- if (!groupLabels.containsKey(calcId))
- {
- groupLabels.put(calcId, new HashMap<Integer, List<String>>());
- }
- Map<Integer, List<String>> groupLabelsForCalcId = groupLabels
- .get(calcId);
- if (groupLabelsForCalcId.containsKey(aa.graphGroup))
- {
- if (!groupLabelsForCalcId.get(aa.graphGroup).contains(
- displayLabel))
- {
- groupLabelsForCalcId.get(aa.graphGroup).add(displayLabel);
- }
- }
- else
- {
- groupLabelsForCalcId.put(aa.graphGroup, labelAsList);
- }
- }
- else
- /*
- * 'Simple case' - not a grouped annotation type - list of one label
- * only
- */
- {
- String rememberAs = calcId + "!" + displayLabel;
- if (aa.visible && !addedToShown.contains(rememberAs))
- {
- if (!shownTypes.containsKey(calcId))
- {
- shownTypes.put(calcId, new ArrayList<List<String>>());
- }
- shownTypes.get(calcId).add(labelAsList);
- addedToShown.add(rememberAs);
- }
- else
- {
- if (!aa.visible && !addedToHidden.contains(rememberAs))
- {
- if (!hiddenTypes.containsKey(calcId))
- {
- hiddenTypes.put(calcId, new ArrayList<List<String>>());
- }
- hiddenTypes.get(calcId).add(labelAsList);
- addedToHidden.add(rememberAs);
- }
- }
- }
- }
- }
- /*
- * finally add the 'composite group labels' to the appropriate lists,
- * depending on whether the group is identified as visible or hidden
- */
- for (String calcId : groupLabels.keySet())
+ List<SequenceI> forSequences = null;
+ final SequenceGroup selectionGroup = ap.av.getSelectionGroup();
+ if (selectionGroup != null && selectionGroup.getSize() > 0)
{
- for (int group : groupLabels.get(calcId).keySet())
- {
- final List<String> groupLabel = groupLabels.get(calcId).get(group);
- if (visibleGraphGroups.get(group))
- {
- if (!shownTypes.containsKey(calcId))
- {
- shownTypes.put(calcId, new ArrayList<List<String>>());
- }
- shownTypes.get(calcId).add(groupLabel);
- }
- else
- {
- if (!hiddenTypes.containsKey(calcId))
- {
- hiddenTypes.put(calcId, new ArrayList<List<String>>());
- }
- hiddenTypes.get(calcId).add(groupLabel);
- }
- }
+ forSequences = selectionGroup.getSequences();
}
- }
-
- /**
- * 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.
- *
- * @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)
+ else
{
- if (ann.graph == AlignmentAnnotation.LINE_GRAPH && ann.visible)
- {
- int gg = ann.graphGroup;
- if (gg > -1)
- {
- result.set(gg);
- }
- }
+ forSequences = seq == null ? Collections.<SequenceI> emptyList()
+ : Arrays.asList(seq);
}
- return result;
+ return forSequences;
}
/**
*
* @param showOrHideMenu
* the menu to add to
+ * @param forSequences
+ * the sequences whose annotations may be shown or hidden
* @param calcId
* @param types
* the label to add
* type, else hide
*/
protected void addAnnotationTypeToShowHide(JMenu showOrHideMenu,
- String calcId, final List<String> types, final boolean allTypes,
+ final List<SequenceI> forSequences, String calcId,
+ final List<String> types, final boolean allTypes,
final boolean actionIsShow)
{
String label = types.toString(); // [a, b, c]
@Override
public void actionPerformed(ActionEvent e)
{
- showHideAnnotation_actionPerformed(types, allTypes, actionIsShow);
+ showHideAnnotation_actionPerformed(types, forSequences, 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.
+ * to show or hide for the specified sequences.
*
* @param types
+ * @param forSequences
* @param anyType
* @param doShow
*/
protected void showHideAnnotation_actionPerformed(
- Collection<String> types, boolean anyType, boolean doShow)
+ Collection<String> types, List<SequenceI> forSequences,
+ boolean anyType, boolean doShow)
{
for (AlignmentAnnotation aa : ap.getAlignment()
.getAlignmentAnnotation())
{
- // TODO: select by calcId (source of annotation) as well as label
- // e.g. by refactoring of buildAnnotationTypeMenus to as
- // to construct the actionPerformed methods as the calcId/labels are found
if (anyType || types.contains(aa.label))
{
if ((aa.sequenceRef != null)
- && ap.av.getSelectionGroup().getSequences()
- .contains(aa.sequenceRef))
+ && forSequences.contains(aa.sequenceRef))
{
aa.visible = doShow;
}
.getString("label.show_annotations"));
hideAnnotationsMenu.setText(MessageManager
.getString("label.hide_annotations"));
- configureReferenceAnnotationsMenu();
sequenceFeature.setText(MessageManager
.getString("label.create_sequence_feature"));
sequenceFeature.addActionListener(new ActionListener()
add(groupMenu);
add(sequenceMenu);
this.add(structureMenu);
+ // annotations configuration panel suppressed for now
// groupMenu.add(chooseAnnotations);
- groupMenu.add(showAnnotationsMenu);
- groupMenu.add(hideAnnotationsMenu);
- groupMenu.add(addDatasequenceAnnotations);
+
+ /*
+ * Add show/hide annotations to either Selection menu (if a selection group
+ * in force), else to the Sequence menu.
+ */
+ SequenceGroup sg = this.ap.av.getSelectionGroup();
+ if (sg != null && sg.getSize() > 0)
+ {
+ groupMenu.add(showAnnotationsMenu);
+ groupMenu.add(hideAnnotationsMenu);
+ groupMenu.add(addReferenceAnnotations);
+ }
+ else
+ {
+ sequenceMenu.add(showAnnotationsMenu);
+ sequenceMenu.add(hideAnnotationsMenu);
+ sequenceMenu.add(addReferenceAnnotations);
+ }
groupMenu.add(editMenu);
groupMenu.add(outputMenu);
groupMenu.add(sequenceFeature);
/**
* Check for any annotations on the underlying dataset sequences (for the
- * current selection group) which are not on the alignment. If any are found,
- * enable the option to add them to the alignment. The criteria for 'on the
- * alignment' is finding an annotation that matches on
- * sequenceRef.datasetSequence, calcId and label.
+ * current selection group) which are not on the alignment annotations for the
+ * sequence. If any are found, enable the option to add them to the alignment.
+ * The criteria for 'on the alignment' is finding an alignment annotation on
+ * the sequence, that matches on calcId and label. A tooltip is also
+ * constructed that displays the source (calcId) and type (label) of the
+ * annotations that can be added.
+ *
+ * @param menuItem
+ * @param forSequences
*/
- protected void configureReferenceAnnotationsMenu()
+ protected void configureReferenceAnnotationsMenu(
+ JMenuItem menuItem, List<SequenceI> forSequences)
{
- addDatasequenceAnnotations.setText(MessageManager
+ menuItem.setText(MessageManager
.getString("label.add_reference_annotations"));
- addDatasequenceAnnotations.setEnabled(false);
+ menuItem.setEnabled(false);
+ if (forSequences == null)
+ {
+ return;
+ }
/*
- * Temporary store so we can write distinct calcId / type pairs on the
- * tooltip.
+ * Temporary store to hold distinct calcId / type pairs for the tooltip.
+ * Using TreeMap means calcIds are shown in alphabetical order.
*/
- Map<String, String> tipEntries = new HashMap<String, String>();
+ Map<String, String> tipEntries = new TreeMap<String, String>();
StringBuilder tooltip = new StringBuilder(64);
tooltip.append(MessageManager.getString("label.add_annotations_for"));
- // this menu option only applies for a Selection
- if (this.ap.av.getSelectionGroup() == null)
- {
- return;
- }
-
/*
* For each sequence selected in the alignment, make a list of any
* annotations on the underlying dataset sequence which are not already on
*
* Build a map of { alignmentSequence, <List of annotations to add> }
*/
- final Map<SequenceI, List<AlignmentAnnotation>> candidates = new HashMap<SequenceI, List<AlignmentAnnotation>>();
- for (SequenceI seq : this.ap.av.getSelectionGroup().getSequences())
+ final Map<SequenceI, List<AlignmentAnnotation>> candidates = new LinkedHashMap<SequenceI, List<AlignmentAnnotation>>();
+ for (SequenceI seq : forSequences)
{
SequenceI dataset = seq.getDatasetSequence();
if (dataset == null)
* Found annotations that could be added. Enable the menu item, and
* configure its tooltip and action.
*/
- addDatasequenceAnnotations.setEnabled(true);
+ menuItem.setEnabled(true);
for (String calcId : tipEntries.keySet())
{
tooltip.append("<br/>" + calcId + "/" + tipEntries.get(calcId));
}
String tooltipText = JvSwingUtils.wrapTooltip(true,
tooltip.toString());
- addDatasequenceAnnotations.setToolTipText(tooltipText);
+ menuItem.setToolTipText(tooltipText);
- addDatasequenceAnnotations.addActionListener(new ActionListener()
+ menuItem.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
protected void addReferenceAnnotations_actionPerformed(
Map<SequenceI, List<AlignmentAnnotation>> candidates)
{
+ /*
+ * Add annotations at the top of the annotation, in the same order as their
+ * related sequences.
+ */
+ int insertPosition = 0;
for (SequenceI seq : candidates.keySet())
{
for (AlignmentAnnotation ann : candidates.get(seq))
}
copyAnn.restrict(startRes, endRes);
- // add to the sequence (sets correct copyAnn.datasetSequence)
+ // add to the sequence (sets copyAnn.datasetSequence)
seq.addAlignmentAnnotation(copyAnn);
// adjust for gaps
copyAnn.adjustForAlignment();
// add to the alignment and set visible
- this.ap.getAlignment().addAnnotation(copyAnn);
+ this.ap.getAlignment().addAnnotation(copyAnn, insertPosition++);
copyAnn.visible = true;
}
}
if (sg != null)
{
if (sequence == null)
+ {
sequence = sg.getSequenceAt(0);
+ }
EditNameDialog dialog = new EditNameDialog(
sequence.getSequenceAsString(sg.getStartRes(),