From: gmungoc Date: Wed, 1 Mar 2017 14:38:50 +0000 (+0000) Subject: Merge branch 'features/JAL-2295setChimeraAttributes' into X-Git-Tag: Release_2_10_2~3^2~173 X-Git-Url: http://source.jalview.org/gitweb/?a=commitdiff_plain;h=4762b29420ed4df01b55a8afe5ab05467aaf41a9;p=jalview.git Merge branch 'features/JAL-2295setChimeraAttributes' into merges/develop_JAL2295setChimeraAttributes Conflicts: src/jalview/ext/rbvi/chimera/ChimeraCommands.java src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java src/jalview/gui/ChimeraViewFrame.java test/jalview/ext/rbvi/chimera/ChimeraCommandsTest.java --- 4762b29420ed4df01b55a8afe5ab05467aaf41a9 diff --cc resources/lang/Messages.properties index f720f39,f1e371d..25f65ab --- a/resources/lang/Messages.properties +++ b/resources/lang/Messages.properties @@@ -620,6 -618,6 +620,8 @@@ label.web_services = Web Service label.right_click_to_edit_currently_selected_parameter = Right click to edit currently selected parameter. label.let_jmol_manage_structure_colours = Let Jmol manage structure colours label.let_chimera_manage_structure_colours = Let Chimera manage structure colours ++label.fetch_chimera_attributes = Fetch Chimera attributes ++label.fetch_chimera_attributes_tip = Copy Chimera attribute to Jalview feature label.marks_leaves_tree_not_associated_with_sequence = Marks leaves of tree not associated with a sequence label.index_web_services_menu_by_host_site = Index web services in menu by the host site label.option_want_informed_web_service_URL_cannot_be_accessed_jalview_when_starts_up = Check this option if you want to be informed
when a web service URL cannot be accessed by Jalview
when it starts up @@@ -1271,21 -1276,4 +1275,21 @@@ label.SEQUENCE_ID_no_longer_used = $SEQ label.SEQUENCE_ID_for_DB_ACCESSION1 = Please review your URL links in the 'Connections' tab of the Preferences window: label.SEQUENCE_ID_for_DB_ACCESSION2 = URL links using '$SEQUENCE_ID$' for DB accessions now use '$DB_ACCESSION$'. label.do_not_display_again = Do not display this message again +exception.url_cannot_have_miriam_id = {0} is a MIRIAM id and cannot be used as a custom url name +exception.url_cannot_have_duplicate_id = {0} cannot be used as a label for more than one line +label.filter = Filter text: +action.customfilter = Custom only +action.showall = Show All +label.insert = Insert: +action.seq_id = $SEQUENCE_ID$ +action.db_acc = $DB_ACCESSION$ +label.primary = Double Click +label.inmenu = In Menu +label.id = ID +label.database = Database +label.urltooltip = Only one url, which must use a sequence id, can be selected for the 'On Click' option +label.edit_sequence_url_link = Edit sequence URL link +warn.name_cannot_be_duplicate = User-defined URL names must be unique and cannot be MIRIAM ids +label.invalid_name = Invalid Name ! label.output_seq_details = Output Sequence Details to list all database references - label.urllinks = Links ++label.urllinks = Links diff --cc src/jalview/ext/rbvi/chimera/ChimeraCommands.java index 8f6b2f1,07c0015..c4aa2e7 --- a/src/jalview/ext/rbvi/chimera/ChimeraCommands.java +++ b/src/jalview/ext/rbvi/chimera/ChimeraCommands.java @@@ -47,28 -47,34 +47,34 @@@ import java.util.Map public class ChimeraCommands { + public static final String NAMESPACE_PREFIX = "jv_"; + /** - * utility to construct the commands to colour chains by the given alignment - * for passing to Chimera - * - * @returns Object[] { Object[] { , + * Constructs Chimera commands to colour residues as per the Jalview alignment * + * @param ssm + * @param files + * @param sequence + * @param sr + * @param fr + * @param alignment + * @return */ - public static StructureMappingcommandSet getColourBySequenceCommand( + public static StructureMappingcommandSet[] getColourBySequenceCommand( StructureSelectionManager ssm, String[] files, SequenceI[][] sequence, SequenceRenderer sr, FeatureRenderer fr, AlignmentI alignment) { - Map>>> colourMap = buildColoursMap( - ssm, files, sequence, sr, fr, alignment); - Map colourMap = buildColoursMap( - ssm, files, sequence, sr, fr, alignment); ++ Map colourMap = buildColoursMap(ssm, files, ++ sequence, sr, fr, alignment); List colourCommands = buildColourCommands(colourMap); StructureMappingcommandSet cs = new StructureMappingcommandSet( ChimeraCommands.class, null, - colourCommands.toArray(new String[0])); + colourCommands.toArray(new String[colourCommands.size()])); - return cs; + return new StructureMappingcommandSet[] { cs }; } /** @@@ -106,43 -113,59 +113,58 @@@ } sb.append("color ").append(colourCode).append(" "); firstColour = false; - boolean firstModelForColour = true; - final Map>> colourData = colourMap - .get(colour); - for (Integer model : colourData.keySet()) - final AtomSpecModel colourData = colourMap - .get(colour); ++ final AtomSpecModel colourData = colourMap.get(colour); + sb.append(colourData.getAtomSpec()); + } + commands.add(sb.toString()); + return commands; + } + + /** + * Traverses a map of { modelNumber, {chain, {list of from-to ranges} } } and + * builds a Chimera format atom spec + * + * @param modelAndChainRanges + */ + protected static String getAtomSpec( + Map>> modelAndChainRanges) + { + StringBuilder sb = new StringBuilder(128); + boolean firstModelForColour = true; + for (Integer model : modelAndChainRanges.keySet()) + { + boolean firstPositionForModel = true; + if (!firstModelForColour) { - boolean firstPositionForModel = true; - if (!firstModelForColour) - { - sb.append("|"); - } - firstModelForColour = false; - sb.append("#").append(model).append(":"); + sb.append("|"); + } + firstModelForColour = false; + sb.append("#").append(model).append(":"); - final Map> modelData = colourData.get(model); - for (String chain : modelData.keySet()) + final Map> modelData = modelAndChainRanges + .get(model); + for (String chain : modelData.keySet()) + { + boolean hasChain = !"".equals(chain.trim()); + for (int[] range : modelData.get(chain)) { - boolean hasChain = !"".equals(chain.trim()); - for (int[] range : modelData.get(chain)) + if (!firstPositionForModel) { - if (!firstPositionForModel) - { - sb.append(","); - } - if (range[0] == range[1]) - { - sb.append(range[0]); - } - else - { - sb.append(range[0]).append("-").append(range[1]); - } - if (hasChain) - { - sb.append(".").append(chain); - } - firstPositionForModel = false; + sb.append(","); + } + if (range[0] == range[1]) + { + sb.append(range[0]); + } + else + { + sb.append(range[0]).append("-").append(range[1]); + } + if (hasChain) + { + sb.append(".").append(chain); } + firstPositionForModel = false; } } } @@@ -230,8 -252,8 +251,8 @@@ // final colour range if (lastColour != null) { - addColourRange(colourMap, lastColour, pdbfnum, startPos, - lastPos, lastChain); - addRange(colourMap, lastColour, pdbfnum, startPos, - lastPos, lastChain); ++ addColourRange(colourMap, lastColour, pdbfnum, startPos, lastPos, ++ lastChain); } // break; } @@@ -251,44 -273,251 +272,251 @@@ * @param endPos * @param chain */ - protected static void addColourRange( - Map>>> colourMap, - Color colour, int model, int startPos, int endPos, String chain) - protected static void addRange(Map map, ++ protected static void addColourRange(Map map, + Object key, int model, int startPos, int endPos, String chain) { /* * Get/initialize map of data for the colour */ - SortedMap>> colourData = colourMap - .get(colour); - if (colourData == null) + AtomSpecModel atomSpec = map.get(key); + if (atomSpec == null) { - colourMap - .put(colour, - colourData = new TreeMap>>()); + atomSpec = new AtomSpecModel(); + map.put(key, atomSpec); } - /* - * Get/initialize map of data for the colour and model - */ - Map> modelData = colourData.get(model); - if (modelData == null) + atomSpec.addRange(model, startPos, endPos, chain); + } + + /** + * Constructs and returns Chimera commands to set attributes on residues + * corresponding to features in Jalview. Attribute names are the Jalview + * feature type, with a "jv_" prefix. + * + * @param ssm + * @param files + * @param seqs + * @param fr + * @param alignment + * @return + */ + public static StructureMappingcommandSet getSetAttributeCommandsForFeatures( + StructureSelectionManager ssm, String[] files, + SequenceI[][] seqs, FeatureRenderer fr, AlignmentI alignment) + { + Map> featureMap = buildFeaturesMap( + ssm, files, seqs, fr, alignment); + + List commands = buildSetAttributeCommands(featureMap); + + StructureMappingcommandSet cs = new StructureMappingcommandSet( + ChimeraCommands.class, null, + commands.toArray(new String[commands.size()])); + + return cs; + } + + /** + *
+    * Helper method to build a map of 
+    *   { featureType, { feature value, AtomSpecModel } }
+    * 
+ * + * @param ssm + * @param files + * @param seqs + * @param fr + * @param alignment + * @return + */ + protected static Map> buildFeaturesMap( + StructureSelectionManager ssm, String[] files, + SequenceI[][] seqs, FeatureRenderer fr, AlignmentI alignment) + { + Map> theMap = new LinkedHashMap>(); + + List visibleFeatures = fr.getDisplayedFeatureTypes(); + if (visibleFeatures.isEmpty()) { - colourData.put(model, modelData = new TreeMap>()); + return theMap; } - + - /* - * Get/initialize map of data for colour, model and chain - */ - List chainData = modelData.get(chain); - if (chainData == null) + for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++) + { + StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]); + + if (mapping == null || mapping.length < 1) + { + continue; + } + + for (int seqNo = 0; seqNo < seqs[pdbfnum].length; seqNo++) + { + for (int m = 0; m < mapping.length; m++) + { + final SequenceI seq = seqs[pdbfnum][seqNo]; + int sp = alignment.findIndex(seq); + if (mapping[m].getSequence() == seq && sp > -1) + { + /* + * found a sequence with a mapping to a structure; + * now scan its features + */ + SequenceI asp = alignment.getSequenceAt(sp); + + scanSequenceFeatures(visibleFeatures, mapping[m], asp, theMap, + pdbfnum); + } + } + } + } + return theMap; + } + + /** + * Inspect features on the sequence; for each feature that is visible, + * determine its mapped ranges in the structure (if any) according to the + * given mapping, and add them to the map + * + * @param visibleFeatures + * @param mapping + * @param seq + * @param theMap + * @param modelNumber + */ + protected static void scanSequenceFeatures(List visibleFeatures, + StructureMapping mapping, SequenceI seq, + Map> theMap, int modelNumber) + { + SequenceFeature[] sfs = seq.getSequenceFeatures(); + if (sfs == null) + { + return; + } + + for (SequenceFeature sf : sfs) + { + String type = sf.getType(); + + /* + * Only copy visible features, don't copy any which originated + * from Chimera, and suppress uninteresting ones (e.g. RESNUM) + */ + boolean isFromViewer = JalviewChimeraBinding.CHIMERA_FEATURE_GROUP + .equals(sf.getFeatureGroup()); + if (isFromViewer || !visibleFeatures.contains(type)) + { + continue; + } + List mappedRanges = mapping.getPDBResNumRanges(sf.getBegin(), + sf.getEnd()); + + if (!mappedRanges.isEmpty()) + { + String value = sf.getDescription(); + if (value == null || value.length() == 0) + { + value = type; + } + float score = sf.getScore(); + if (score != 0f && !Float.isNaN(score)) + { + value = Float.toString(score); + } + Map featureValues = theMap.get(type); + if (featureValues == null) + { + featureValues = new HashMap(); + theMap.put(type, featureValues); + } + for (int[] range : mappedRanges) + { - addRange(featureValues, value, modelNumber, range[0], range[1], ++ addColourRange(featureValues, value, modelNumber, range[0], range[1], + mapping.getChain()); + } + } + } + } + + /** + * Traverse the map of features/values/models/chains/positions to construct a + * list of 'setattr' commands (one per distinct feature type and value). + *

+ * The format of each command is + * + *

+    * 
setattr r " " #modelnumber:range.chain + * e.g. setattr r jv:chain #0:2.B,4.B,9-12.B|#1:1.A,2-6.A,... + *
+ *
+ * + * @param featureMap + * @return + */ + protected static List buildSetAttributeCommands( + Map> featureMap) + { + List commands = new ArrayList(); + for (String featureType : featureMap.keySet()) { - modelData.put(chain, chainData = new ArrayList()); + String attributeName = makeAttributeName(featureType); + + /* + * clear down existing attributes for this feature + */ + // 'problem' - sets attribute to None on all residues - overkill? + // commands.add("~setattr r " + attributeName + " :*"); + + Map values = featureMap.get(featureType); + for (Object value : values.keySet()) + { + /* + * for each distinct value recorded for this feature type, + * add a command to set the attribute on the mapped residues + */ + StringBuilder sb = new StringBuilder(128); + sb.append("setattr r ").append(attributeName).append(" \"") + .append(value.toString()).append("\" "); + sb.append(values.get(value).getAtomSpec()); + commands.add(sb.toString()); + } } + return commands; + } + + /** + * Makes a prefixed and valid Chimera attribute name. A jv_ prefix is applied + * for a 'Jalview' namespace, and any non-alphanumeric character is converted + * to an underscore. + * + * @param featureType + * @return
+    * @see https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/setattr.html
+    * 
+ */ + protected static String makeAttributeName(String featureType) + { + StringBuilder sb = new StringBuilder(); + if (featureType != null) + { + for (char c : featureType.toCharArray()) + { + sb.append(Character.isLetterOrDigit(c) ? c : '_'); + } + } + String attName = NAMESPACE_PREFIX + sb.toString(); + /* - * Add the start/end positions + * Chimera treats an attribute name ending in 'color' as colour-valued; + * Jalview doesn't, so prevent this by appending an underscore */ - chainData.add(new int[] { startPos, endPos }); + if (attName.toUpperCase().endsWith("COLOR")) + { + attName += "_"; + } + + return attName; } } diff --cc src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java index b05c168,75ddc9c..2042ac4 --- a/src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java +++ b/src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java @@@ -735,13 -771,6 +747,14 @@@ public abstract class JalviewChimeraBin */ public abstract void refreshPdbEntries(); + /** + * map between index of model filename returned from getPdbFile and the first + * index of models from this file in the viewer. Note - this is not trimmed - + * use getPdbFile to get number of unique models. + */ + private int _modelFileNameMap[]; + ++ // //////////////////////////////// // /StructureListener @Override diff --cc src/jalview/gui/ChimeraViewFrame.java index 530f4fe,4c38898..9b0d85b --- a/src/jalview/gui/ChimeraViewFrame.java +++ b/src/jalview/gui/ChimeraViewFrame.java @@@ -21,9 -21,12 +21,10 @@@ package jalview.gui; import jalview.bin.Cache; -import jalview.datamodel.Alignment; import jalview.datamodel.AlignmentI; -import jalview.datamodel.ColumnSelection; import jalview.datamodel.PDBEntry; import jalview.datamodel.SequenceI; + import jalview.ext.rbvi.chimera.ChimeraCommands; import jalview.ext.rbvi.chimera.JalviewChimeraBinding; import jalview.gui.StructureViewer.ViewerType; import jalview.io.DataSourceType; @@@ -35,18 -48,35 +36,24 @@@ import jalview.util.Platform import jalview.ws.dbsources.Pdb; import java.awt.event.ActionEvent; + import java.awt.event.ActionListener; -import java.awt.event.ItemEvent; -import java.awt.event.ItemListener; + import java.awt.event.MouseAdapter; + import java.awt.event.MouseEvent; -import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.FileReader; import java.io.IOException; import java.io.InputStream; -import java.io.PrintWriter; import java.util.ArrayList; + import java.util.Collections; import java.util.List; import java.util.Random; -import java.util.Vector; import javax.swing.JCheckBoxMenuItem; -import javax.swing.JColorChooser; import javax.swing.JInternalFrame; + import javax.swing.JMenu; + import javax.swing.JMenuItem; -import javax.swing.JOptionPane; import javax.swing.event.InternalFrameAdapter; import javax.swing.event.InternalFrameEvent; -import javax.swing.event.MenuEvent; -import javax.swing.event.MenuListener; /** * GUI elements for handling an external chimera display @@@ -58,8 -88,8 +65,6 @@@ public class ChimeraViewFrame extends S { private JalviewChimeraBinding jmb; -- private boolean allChainsSelected = false; -- private IProgressIndicator progressBar = null; /* @@@ -85,10 -111,172 +90,102 @@@ .getString("label.colour_with_chimera")); viewerColour.setToolTipText(MessageManager .getString("label.let_chimera_manage_structure_colours")); - helpItem.setText(MessageManager.getString("label.chimera_help")); - seqColour.setSelected(jmb.isColourBySequence()); - viewerColour.setSelected(!jmb.isColourBySequence()); - if (_colourwith == null) - { - _colourwith = new Vector(); - } - if (_alignwith == null) - { - _alignwith = new Vector(); - } - - // save As not yet implemented - savemenu.setVisible(false); - ViewSelectionMenu seqColourBy = new ViewSelectionMenu( - MessageManager.getString("label.colour_by"), this, _colourwith, - new ItemListener() - { - @Override - public void itemStateChanged(ItemEvent e) - { - if (!seqColour.isSelected()) - { - seqColour.doClick(); - } - else - { - // update the Chimera display now. - seqColour_actionPerformed(null); - } - } - }); - viewMenu.add(seqColourBy); + helpItem.setText(MessageManager.getString("label.chimera_help")); + savemenu.setVisible(false); // not yet implemented viewMenu.add(fitToWindow); + - final ItemListener handler; - JMenu alpanels = new ViewSelectionMenu( - MessageManager.getString("label.superpose_with"), this, - _alignwith, handler = new ItemListener() - { - @Override - public void itemStateChanged(ItemEvent e) - { - alignStructs.setEnabled(_alignwith.size() > 0); - alignStructs.setToolTipText(MessageManager - .formatMessage( - "label.align_structures_using_linked_alignment_views", - new Object[] { new Integer(_alignwith - .size()).toString() })); - } - }); - handler.itemStateChanged(null); - viewerActionMenu.add(alpanels); - viewerActionMenu.addMenuListener(new MenuListener() - { - - @Override - public void menuSelected(MenuEvent e) - { - handler.itemStateChanged(null); - } - - @Override - public void menuDeselected(MenuEvent e) - { - // TODO Auto-generated method stub - } - - @Override - public void menuCanceled(MenuEvent e) - { - // TODO Auto-generated method stub - } - }); - + JMenuItem writeFeatures = new JMenuItem( + MessageManager.getString("label.create_chimera_attributes")); + writeFeatures.setToolTipText(MessageManager + .getString("label.create_chimera_attributes_tip")); + writeFeatures.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + sendFeaturesToChimera(); + } + }); + viewerActionMenu.add(writeFeatures); + - final JMenu fetchAttributes = new JMenu("Fetch Chimera attributes"); - fetchAttributes - .setToolTipText("Copy Chimera attribute to Jalview feature"); ++ final JMenu fetchAttributes = new JMenu( ++ MessageManager.getString("label.fetch_chimera_attributes")); ++ fetchAttributes.setToolTipText(MessageManager ++ .getString("label.fetch_chimera_attributes_tip")); + fetchAttributes.addMouseListener(new MouseAdapter() + { + + @Override + public void mouseEntered(MouseEvent e) + { + buildAttributesMenu(fetchAttributes); + } + }); + viewerActionMenu.add(fetchAttributes); + + } + + /** + * Query Chimera for its residue attribute names and add them as items off the + * attributes menu + * + * @param attributesMenu + */ + protected void buildAttributesMenu(JMenu attributesMenu) + { + List atts = jmb.sendChimeraCommand("list resattr", true); + if (atts == null) + { + return; + } + attributesMenu.removeAll(); + Collections.sort(atts); + for (String att : atts) + { + final String attName = att.split(" ")[1]; + + /* + * ignore 'jv_*' attributes, as these are Jalview features that have + * been transferred to residue attributes in Chimera! + */ + if (!attName.startsWith(ChimeraCommands.NAMESPACE_PREFIX)) + { + JMenuItem menuItem = new JMenuItem(attName); + menuItem.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + getChimeraAttributes(attName); + } + }); + attributesMenu.add(menuItem); + } + } + } + + /** + * Read residues in Chimera with the given attribute name, and set as features + * on the corresponding sequence positions (if any) + * + * @param attName + */ + protected void getChimeraAttributes(String attName) + { + jmb.copyStructureAttributesToFeatures(attName, getAlignmentPanel()); + } + + /** + * Send a command to Chimera to create residue attributes for Jalview features + *

+ * The syntax is: setattr r + *

+ * For example: setattr r jv:chain "Ferredoxin-1, Chloroplastic" #0:94.A + */ + protected void sendFeaturesToChimera() + { + jmb.sendFeaturesToViewer(getAlignmentPanel()); } /** @@@ -269,10 -464,8 +365,10 @@@ */ void initChimera() { - Desktop.addInternalFrame(this, jmb.getViewerTitle("Chimera", true), - getBounds().width, getBounds().height); + jmb.setFinishedInit(false); - jalview.gui.Desktop.addInternalFrame(this, ++ Desktop.addInternalFrame(this, + jmb.getViewerTitle(getViewerName(), true), getBounds().width, + getBounds().height); if (!jmb.launchChimera()) { @@@ -299,8 -491,62 +394,7 @@@ jmb.startChimeraListener(); } - /** - * If the list is not empty, add menu items for 'All' and each individual - * chain to the "View | Show Chain" sub-menu. Multiple selections are allowed. - * - * @param chainNames - */ - @Override - void setChainMenuItems(List chainNames) - { - chainMenu.removeAll(); - if (chainNames == null || chainNames.isEmpty()) - { - return; - } - JMenuItem menuItem = new JMenuItem( - MessageManager.getString("label.all")); - menuItem.addActionListener(new ActionListener() - { - @Override - public void actionPerformed(ActionEvent evt) - { - allChainsSelected = true; - for (int i = 0; i < chainMenu.getItemCount(); i++) - { - if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem) - { - ((JCheckBoxMenuItem) chainMenu.getItem(i)).setSelected(true); - } - } - showSelectedChains(); - allChainsSelected = false; - } - }); - - chainMenu.add(menuItem); - - for (String chainName : chainNames) - { - menuItem = new JCheckBoxMenuItem(chainName, true); - menuItem.addItemListener(new ItemListener() - { - @Override - public void itemStateChanged(ItemEvent evt) - { - if (!allChainsSelected) - { - showSelectedChains(); - } - } - }); - - chainMenu.add(menuItem); - } - } - - /** * Show only the selected chain(s) in the viewer */ @Override @@@ -654,9 -1095,9 +750,9 @@@ { try { - jalview.util.BrowserLauncher + BrowserLauncher .openURL("https://www.cgl.ucsf.edu/chimera/docs/UsersGuide"); -- } catch (Exception ex) ++ } catch (IOException ex) { } } @@@ -750,8 -1287,8 +846,15 @@@ } @Override - protected AAStructureBindingModel getBindingModel() + protected String getViewerName() { - return jmb; + return "Chimera"; + } ++ ++ @Override ++ public void updateTitleAndMenus() ++ { ++ super.updateTitleAndMenus(); ++ viewerActionMenu.setVisible(true); + } } diff --cc test/jalview/ext/rbvi/chimera/ChimeraCommandsTest.java index ffb886c,70c6922..fb442e3 --- a/test/jalview/ext/rbvi/chimera/ChimeraCommandsTest.java +++ b/test/jalview/ext/rbvi/chimera/ChimeraCommandsTest.java @@@ -49,70 -48,104 +48,105 @@@ public class ChimeraCommandsTes public void testBuildColourCommands() { - Map>>> map = new LinkedHashMap>>>(); + Map map = new LinkedHashMap(); - ChimeraCommands.addRange(map, Color.blue, 0, 2, 5, "A"); - ChimeraCommands.addRange(map, Color.blue, 0, 7, 7, "B"); - ChimeraCommands.addRange(map, Color.blue, 0, 9, 23, "A"); - ChimeraCommands.addRange(map, Color.blue, 1, 1, 1, "A"); - ChimeraCommands.addRange(map, Color.blue, 1, 4, 7, "B"); - ChimeraCommands.addRange(map, Color.yellow, 1, 8, 8, "A"); - ChimeraCommands.addRange(map, Color.yellow, 1, 3, 5, "A"); - ChimeraCommands.addRange(map, Color.red, 0, 3, 5, "A"); - ChimeraCommands.addRange(map, Color.red, 0, 6, 9, "A"); + ChimeraCommands.addColourRange(map, Color.blue, 0, 2, 5, "A"); + ChimeraCommands.addColourRange(map, Color.blue, 0, 7, 7, "B"); + ChimeraCommands.addColourRange(map, Color.blue, 0, 9, 23, "A"); + ChimeraCommands.addColourRange(map, Color.blue, 1, 1, 1, "A"); + ChimeraCommands.addColourRange(map, Color.blue, 1, 4, 7, "B"); + ChimeraCommands.addColourRange(map, Color.yellow, 1, 8, 8, "A"); + ChimeraCommands.addColourRange(map, Color.yellow, 1, 3, 5, "A"); + ChimeraCommands.addColourRange(map, Color.red, 0, 3, 5, "A"); ++ ChimeraCommands.addColourRange(map, Color.red, 0, 6, 9, "A"); // Colours should appear in the Chimera command in the order in which - // they were added; within colour, by model, by chain, and positions as - // added + // they were added; within colour, by model, by chain, ranges in start order String command = ChimeraCommands.buildColourCommands(map).get(0); assertEquals( - "color #0000ff #0:2-5.A,9-23.A,7.B|#1:1.A,4-7.B; color #ffff00 #1:8.A,3-5.A; color #ff0000 #0:3-5.A", - command); + command, + "color #0000ff #0:2-5.A,9-23.A,7.B|#1:1.A,4-7.B; color #ffff00 #1:3-5.A,8.A; color #ff0000 #0:3-9.A"); + } + + @Test(groups = { "Functional" }) + public void testBuildSetAttributeCommands() + { + /* + * make a map of { featureType, {featureValue, {residue range specification } } } + */ + Map> featuresMap = new LinkedHashMap>(); + Map featureValues = new HashMap(); + + /* + * start with just one feature/value... + */ + featuresMap.put("chain", featureValues); - ChimeraCommands.addRange(featureValues, "X", 0, 8, 20, "A"); ++ ChimeraCommands.addColourRange(featureValues, "X", 0, 8, 20, "A"); + + List commands = ChimeraCommands + .buildSetAttributeCommands(featuresMap); + assertEquals(1, commands.size()); + + /* + * feature name gets a jv_ namespace prefix + * feature value is quoted in case it contains spaces + */ + assertEquals(commands.get(0), "setattr r jv_chain \"X\" #0:8-20.A"); + + // add same feature value, overlapping range - ChimeraCommands.addRange(featureValues, "X", 0, 3, 9, "A"); ++ ChimeraCommands.addColourRange(featureValues, "X", 0, 3, 9, "A"); + // same feature value, contiguous range - ChimeraCommands.addRange(featureValues, "X", 0, 21, 25, "A"); ++ ChimeraCommands.addColourRange(featureValues, "X", 0, 21, 25, "A"); + commands = ChimeraCommands.buildSetAttributeCommands(featuresMap); + assertEquals(1, commands.size()); + assertEquals(commands.get(0), "setattr r jv_chain \"X\" #0:3-25.A"); + + // same feature value and model, different chain - ChimeraCommands.addRange(featureValues, "X", 0, 21, 25, "B"); ++ ChimeraCommands.addColourRange(featureValues, "X", 0, 21, 25, "B"); + // same feature value and chain, different model - ChimeraCommands.addRange(featureValues, "X", 1, 26, 30, "A"); ++ ChimeraCommands.addColourRange(featureValues, "X", 1, 26, 30, "A"); + commands = ChimeraCommands.buildSetAttributeCommands(featuresMap); + assertEquals(1, commands.size()); + assertEquals(commands.get(0), + "setattr r jv_chain \"X\" #0:3-25.A,21-25.B|#1:26-30.A"); + + // same feature, different value - ChimeraCommands.addRange(featureValues, "Y", 0, 40, 50, "A"); ++ ChimeraCommands.addColourRange(featureValues, "Y", 0, 40, 50, "A"); + commands = ChimeraCommands.buildSetAttributeCommands(featuresMap); + assertEquals(2, commands.size()); + // commands are ordered by feature type but not by value + // so use contains to test for the expected command: + assertTrue(commands + .contains("setattr r jv_chain \"X\" #0:3-25.A,21-25.B|#1:26-30.A")); + assertTrue(commands.contains("setattr r jv_chain \"Y\" #0:40-50.A")); + + featuresMap.clear(); + featureValues.clear(); + featuresMap.put("side-chain binding!", featureValues); - ChimeraCommands.addRange(featureValues, "metal ion!", 0, 7, 15, "A"); ++ ChimeraCommands.addColourRange(featureValues, "metal ion!", 0, 7, 15, ++ "A"); + // feature names are sanitised to change space or hyphen to underscore + commands = ChimeraCommands.buildSetAttributeCommands(featuresMap); + assertTrue(commands + .contains("setattr r jv_side_chain_binding_ \"metal ion!\" #0:7-15.A")); + } + + /** + * Tests for the method that prefixes and sanitises a feature name so it can + * be used as a valid, namespaced attribute name in Chimera + */ + @Test(groups = { "Functional" }) + public void testMakeAttributeName() + { + assertEquals(ChimeraCommands.makeAttributeName(null), "jv_"); + assertEquals(ChimeraCommands.makeAttributeName(""), "jv_"); + assertEquals(ChimeraCommands.makeAttributeName("helix"), "jv_helix"); + assertEquals(ChimeraCommands.makeAttributeName("Hello World 24"), + "jv_Hello_World_24"); + assertEquals( + ChimeraCommands.makeAttributeName("!this is-a_very*{odd(name"), + "jv__this_is_a_very__odd_name"); + // name ending in color gets underscore appended + assertEquals(ChimeraCommands.makeAttributeName("helixColor"), + "jv_helixColor_"); } } diff --cc test/jalview/gui/AnnotationChooserTest.java index 38c1855,38c1855..cf97711 --- a/test/jalview/gui/AnnotationChooserTest.java +++ b/test/jalview/gui/AnnotationChooserTest.java @@@ -60,6 -60,6 +60,11 @@@ import org.testng.annotations.Test */ public class AnnotationChooserTest { ++ /* ++ * number of automatically computed annotation rows ++ * (Conservation, Quality, Consensus, Occupancy) ++ */ ++ private static final int AUTOCALCD = 4; @BeforeClass(alwaysRun = true) public void setUpJvOptionPane() @@@ -326,7 -326,7 +331,7 @@@ types = AnnotationChooser.getAnnotationTypes( parentPanel.getAlignment(), false); -- assertEquals("Not six annotation types", 6, types.size()); ++ assertEquals("Not six annotation types", 7, types.size()); assertTrue("IUPRED missing", types.contains("IUPRED")); assertTrue("JMol missing", types.contains("JMol")); assertTrue("Beauty missing", types.contains("Beauty")); @@@ -334,6 -334,6 +339,7 @@@ assertTrue("Consensus missing", types.contains("Consensus")); assertTrue("Quality missing", types.contains("Quality")); assertTrue("Conservation missing", types.contains("Conservation")); ++ assertTrue("Occupancy missing", types.contains("Occupancy")); } /** @@@ -357,18 -357,18 +363,19 @@@ AlignmentAnnotation[] anns = parentPanel.getAlignment() .getAlignmentAnnotation(); -- assertTrue(anns[5].visible); // JMol for seq3 -- assertTrue(anns[7].visible); // JMol for seq1 ++ assertTrue(anns[AUTOCALCD + 2].visible); // JMol for seq3 ++ assertTrue(anns[AUTOCALCD + 4].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 ++ assertTrue(anns[3].visible); // Occupancy ++ assertTrue(anns[4].visible); // IUPred for seq0 ++ assertTrue(anns[5].visible); // Beauty ++ assertFalse(anns[6].visible); // JMol for seq3 - not selected but hidden ++ assertTrue(anns[7].visible); // IUPRED for seq2 ++ assertFalse(anns[8].visible); // JMol for seq1 - selected and hidden } /** @@@ -395,17 -395,17 +402,18 @@@ AlignmentAnnotation[] anns = parentPanel.getAlignment() .getAlignmentAnnotation(); -- assertTrue(anns[7].visible); // JMol for seq1 ++ assertTrue(anns[AUTOCALCD + 4].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 ++ assertTrue(anns[3].visible); // Occupancy ++ assertTrue(anns[4].visible); // IUPred for seq0 ++ assertTrue(anns[5].visible); // Beauty ++ assertTrue(anns[6].visible); // JMol for seq3 not in selection group ++ assertTrue(anns[7].visible); // IUPRED for seq2 ++ assertFalse(anns[8].visible); // JMol for seq1 in selection group } /** @@@ -435,19 -435,19 +443,20 @@@ // select JMol - all hidden setSelected(typeCheckbox, true); -- assertFalse(anns[5].visible); // JMol for seq3 -- assertFalse(anns[7].visible); // JMol for seq1 ++ assertFalse(anns[AUTOCALCD + 2].visible); // JMol for seq3 ++ assertFalse(anns[AUTOCALCD + 4].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 ++ assertTrue(anns[3].visible); // Occupancy ++ assertTrue(anns[4].visible); // IUPred for seq0 ++ assertTrue(anns[5].visible); // Beauty ++ assertTrue(anns[6].visible); // JMol for seq3 ++ assertTrue(anns[7].visible); // IUPRED for seq2 ++ assertTrue(anns[8].visible); // JMol for seq1 } /** @@@ -510,18 -510,18 +519,19 @@@ setSelected(allSequencesCheckbox, true); setSelected(hideCheckbox, true); setSelected(getTypeCheckbox("JMol"), true); -- assertFalse(anns[5].visible); // JMol for seq3 -- assertFalse(anns[7].visible); // JMol for seq1 ++ assertFalse(anns[AUTOCALCD + 2].visible); // JMol for seq3 ++ assertFalse(anns[AUTOCALCD + 4].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 ++ assertTrue(anns[3].visible); // Occupancy ++ assertTrue(anns[4].visible); // IUPred for seq0 ++ assertTrue(anns[5].visible); // Beauty ++ assertTrue(anns[6].visible); // JMol for seq3 ++ assertTrue(anns[7].visible); // IUPRED for seq2 ++ assertTrue(anns[8].visible); // JMol for seq1 } /** @@@ -551,19 -551,19 +561,20 @@@ setSelected(hideCheckbox, true); setSelected(getTypeCheckbox("JMol"), true); -- assertTrue(anns[5].visible); // JMol for seq3 -- assertFalse(anns[7].visible); // JMol for seq1 ++ assertTrue(anns[AUTOCALCD + 2].visible); // JMol for seq3 ++ assertFalse(anns[AUTOCALCD + 4].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 ++ assertTrue(anns[3].visible); // Occupancy ++ assertTrue(anns[4].visible); // IUPred for seq0 ++ assertTrue(anns[5].visible); // Beauty ++ assertTrue(anns[6].visible); // JMol for seq3 ++ assertTrue(anns[7].visible); // IUPRED for seq2 ++ assertTrue(anns[8].visible); // JMol for seq1 } /** @@@ -592,19 -592,19 +603,20 @@@ 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 ++ assertTrue(anns[AUTOCALCD + 2].visible); // JMol for seq3 ++ assertTrue(anns[AUTOCALCD + 4].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 ++ assertTrue(anns[3].visible); // Occupancy ++ assertTrue(anns[4].visible); // IUPred for seq0 ++ assertTrue(anns[5].visible); // Beauty ++ assertFalse(anns[6].visible); // JMol for seq3 ++ assertTrue(anns[7].visible); // IUPRED for seq2 ++ assertFalse(anns[8].visible); // JMol for seq1 } /** @@@ -633,19 -633,19 +645,20 @@@ // select JMol - should remain visible setSelected(getTypeCheckbox("JMol"), true); -- assertTrue(anns[5].visible); // JMol for seq3 -- assertTrue(anns[7].visible); // JMol for seq1 ++ assertTrue(anns[AUTOCALCD + 2].visible); // JMol for seq3 ++ assertTrue(anns[AUTOCALCD + 4].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 ++ assertTrue(anns[3].visible); // Occupancy ++ assertTrue(anns[4].visible); // IUPred for seq0 ++ assertTrue(anns[5].visible); // Beauty ++ assertTrue(anns[6].visible); // JMol for seq3 not in selection group ++ assertTrue(anns[7].visible); // IUPRED for seq2 ++ assertFalse(anns[8].visible); // JMol for seq1 in selection group } /** @@@ -721,12 -721,12 +734,11 @@@ 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])); ++ assertFalse(testee.isInActionScope(anns[AUTOCALCD])); ++ assertFalse(testee.isInActionScope(anns[AUTOCALCD + 1])); ++ assertFalse(testee.isInActionScope(anns[AUTOCALCD + 2])); ++ assertTrue(testee.isInActionScope(anns[AUTOCALCD + 3])); ++ assertTrue(testee.isInActionScope(anns[AUTOCALCD + 4])); } /** @@@ -747,12 -747,12 +759,11 @@@ 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])); ++ assertTrue(testee.isInActionScope(anns[AUTOCALCD])); ++ assertTrue(testee.isInActionScope(anns[AUTOCALCD + 1])); ++ assertTrue(testee.isInActionScope(anns[AUTOCALCD + 2])); ++ assertFalse(testee.isInActionScope(anns[AUTOCALCD + 3])); ++ assertFalse(testee.isInActionScope(anns[AUTOCALCD + 4])); } /** @@@ -787,11 -787,11 +798,12 @@@ 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 ++ assertTrue(anns[3].visible); // Occupancy ++ assertFalse(anns[4].visible); // IUPRED ++ assertTrue(anns[5].visible); // Beauty (not seq-related) ++ assertFalse(anns[6].visible); // JMol ++ assertFalse(anns[7].visible); // IUPRED ++ assertFalse(anns[8].visible); // JMol // reset - should all be visible testee.resetOriginalState();