Merge branch 'releases/Release_2_11_3_Branch'
[jalview.git] / src / ext / edu / ucsf / rbvi / strucviz2 / StructureManager.java
1 /* vim: set ts=2: */
2 /**
3  * Copyright (c) 2006 The Regents of the University of California.
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *   1. Redistributions of source code must retain the above copyright
10  *      notice, this list of conditions, and the following disclaimer.
11  *   2. Redistributions in binary form must reproduce the above
12  *      copyright notice, this list of conditions, and the following
13  *      disclaimer in the documentation and/or other materials provided
14  *      with the distribution.
15  *   3. Redistributions must acknowledge that this software was
16  *      originally developed by the UCSF Computer Graphics Laboratory
17  *      under support by the NIH National Center for Research Resources,
18  *      grant P41-RR01081.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY
21  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
26  * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
27  * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
28  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
29  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
30  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  *
32  */
33 package ext.edu.ucsf.rbvi.strucviz2;
34
35 import java.io.File;
36 import java.io.IOException;
37 import java.util.ArrayList;
38 import java.util.Arrays;
39 import java.util.Collection;
40 import java.util.HashMap;
41 import java.util.List;
42 import java.util.Locale;
43 import java.util.Map;
44 import java.util.Properties;
45 import java.util.Set;
46
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
49
50 import jalview.bin.Cache;
51 import jalview.gui.Preferences;
52
53 /**
54  * This object maintains the relationship between Chimera objects and Cytoscape
55  * objects.
56  */
57
58 public class StructureManager
59 {
60   /*
61    * Version numbers to build Windows installation paths for 
62    * Chimera  https://www.cgl.ucsf.edu/chimera/download.html
63    * ChimeraX http://www.rbvi.ucsf.edu/chimerax/download.html#release
64    *          https://www.rbvi.ucsf.edu/trac/ChimeraX/wiki/ChangeLog
65    * These are a fallback for Jalview users who don't save path in Preferences;
66    * these will need to be updated as new versions are released;
67    * deliberately not 'final' (so modifiable using Groovy).
68    * 
69    * May 2020: 1.14 is Chimera latest, anticipating a few more...
70    * 0.93 is ChimeraX latest, 1.0 expected soon
71    */
72   private static String[] CHIMERA_VERSIONS = new String[] { "1.18",
73       "1.17.4", "1.17.3", "1.17.2", "1.17.1", "1.17", "1.16.2", "1.16.1",
74       "1.16", "1.15.2", "1.15.1", "1.15", "1.14.2", "1.14.1", "1.14",
75       "1.13.1", "1.13", "1.12.2", "1.12.1", "1.12", "1.11.2", "1.11.2",
76       "1.11.1", "1.11" };
77
78   // Missing 1.1 as this has known bug see JAL-2422
79   private static String[] CHIMERAX_VERSIONS = new String[] { "1.7", "1.6.1",
80       "1.6", "1.5", "1.4", "1.3", "1.2.5", "1.0", "0.93", "0.92", "0.91",
81       "0.9" };
82
83   static final String[] defaultStructureKeys = { "Structure", "pdb",
84       "pdbFileName", "PDB ID", "structure", "biopax.xref.PDB", "pdb_ids",
85       "ModelName", "ModelNumber" };
86
87   static final String[] defaultChemStructKeys = { "Smiles", "smiles",
88       "SMILES" };
89
90   static final String[] defaultResidueKeys = { "FunctionalResidues",
91       "ResidueList", "Residues" };
92
93   public enum ModelType
94   {
95     PDB_MODEL, MODBASE_MODEL, SMILES
96   };
97
98   public static Properties pathProps;
99
100   private String chimeraCommandAttr = "ChimeraCommand";
101
102   private String chimeraOutputTable = "ChimeraTable";
103
104   private String chimeraOutputAttr = "ChimeraOutput";
105
106   private boolean haveGUI = true;
107
108   private ChimeraManager chimeraManager = null;
109
110   static private List<ChimeraStructuralObject> chimSelectionList;
111
112   private boolean ignoreCySelection = false;
113
114   private File configurationDirectory = null;
115
116   private static Logger logger = LoggerFactory
117           .getLogger(ext.edu.ucsf.rbvi.strucviz2.StructureManager.class);
118
119   public StructureManager(boolean haveGUI)
120   {
121     this.haveGUI = haveGUI;
122     // Create the Chimera interface
123     chimeraManager = new ChimeraManager(this);
124     chimSelectionList = new ArrayList<>();
125     pathProps = new Properties();
126   }
127
128   public ChimeraManager getChimeraManager()
129   {
130     return chimeraManager;
131   }
132
133   public boolean openStructures(Collection<List<String>> chimObjNames,
134           ModelType type)
135   {
136     // new models
137     Map<String, List<ChimeraModel>> newModels = new HashMap<>();
138     if (chimObjNames.size() > 0)
139     {
140       List<String> names = chimObjNames.iterator().next();
141       if (names == null)
142       {
143         return false;
144       }
145       for (String chimObjName : names)
146       {
147         // get or open the corresponding models if they already exist
148         List<ChimeraModel> currentModels = chimeraManager
149                 .getChimeraModels(chimObjName, type);
150         if (currentModels.size() == 0)
151         {
152           // open and return models
153           currentModels = chimeraManager.openModel(chimObjName, type);
154           if (currentModels == null)
155           {
156             // failed to open model, continue with next
157             continue;
158           }
159           // if (type == ModelType.SMILES) {
160           // newModels.put("smiles:" + chimObjName, currentModels);
161           // } else {
162           newModels.put(chimObjName, currentModels);
163           // }
164           // for each model
165           for (ChimeraModel currentModel : currentModels)
166           {
167             // if not RIN then associate new model with the Cytoscape
168             // node
169             // if (!currentChimMap.containsKey(currentModel)) {
170             // currentChimMap.put(currentModel, new HashSet<CyIdentifiable>());
171             // }
172           }
173         }
174       }
175     }
176     else
177     {
178       return false;
179     }
180     // update dialog
181     // if (mnDialog != null) {
182     // mnDialog.modelChanged();
183     // }
184     // aTask.associate();
185     return true;
186
187   }
188
189   // TODO: [Release] Handle case where one network is associated with two models
190   // that are opened
191   // at the same time
192   /*
193    * public boolean openStructures(CyNetwork network, Map<CyIdentifiable,
194    * List<String>> chimObjNames, ModelType type) { if
195    * (!chimeraManager.isChimeraLaunched() &&
196    * !chimeraManager.launchChimera(getChimeraPaths(network))) {
197    * logger.error("Chimera could not be launched."); return false; } else if
198    * (chimObjNames.size() == 0) { return false; } else if (network == null) {
199    * return openStructures(chimObjNames.values(), type); }
200    * 
201    * // potential rins Set<CyNetwork> potentialRINs = new HashSet<CyNetwork>();
202    * // attributes List<String> attrsFound = new ArrayList<String>();
203    * attrsFound.
204    * addAll(CytoUtils.getMatchingAttributes(network.getDefaultNodeTable(),
205    * getCurrentStructureKeys(network)));
206    * attrsFound.addAll(CytoUtils.getMatchingAttributes
207    * (network.getDefaultNodeTable(), getCurrentChemStructKeys(network))); // new
208    * models Map<String, List<ChimeraModel>> newModels = new HashMap<String,
209    * List<ChimeraModel>>(); // for each node that has an associated structure
210    * for (CyIdentifiable cyObj : chimObjNames.keySet()) { // get possible res
211    * specs List<String> specsFound = null; if (cyObj instanceof CyNode) {
212    * specsFound = ChimUtils.getResidueKeys(network.getDefaultNodeTable(), cyObj,
213    * attrsFound); } // save node to track its selection and mapping to chimera
214    * objects if (!currentCyMap.containsKey(cyObj)) { currentCyMap.put(cyObj, new
215    * HashSet<ChimeraStructuralObject>()); } // save node to network mapping to
216    * keep track of selection events if (!networkMap.containsKey(cyObj)) {
217    * networkMap.put(cyObj, new HashSet<CyNetwork>()); }
218    * networkMap.get(cyObj).add(network); // for each structure that has to be
219    * opened for (String chimObjName : chimObjNames.get(cyObj)) { // get or open
220    * the corresponding models if they already exist List<ChimeraModel>
221    * currentModels = chimeraManager.getChimeraModels(chimObjName, type); if
222    * (currentModels.size() == 0) { // open and return models currentModels =
223    * chimeraManager.openModel(chimObjName, type); if (currentModels == null) {
224    * // failed to open model, continue with next continue; } // if (type ==
225    * ModelType.SMILES) { // newModels.put("smiles:" + chimObjName,
226    * currentModels); // } else { newModels.put(chimObjName, currentModels); // }
227    * // for each model for (ChimeraModel currentModel : currentModels) { //
228    * check if it is a RIN boolean foundRIN = false; if
229    * (currentModel.getModelType().equals(ModelType.PDB_MODEL)) { // go through
230    * all node annotations and check if any of them is a residue // or a chain if
231    * (cyObj instanceof CyNode && network.containsNode((CyNode) cyObj) &&
232    * specsFound != null && specsFound.size() > 0) { for (String resSpec :
233    * specsFound) { ChimeraStructuralObject res =
234    * ChimUtils.fromAttribute(resSpec, chimeraManager); if (res != null && (res
235    * instanceof ChimeraResidue || res instanceof ChimeraChain)) { // if so,
236    * assume it might be a RIN potentialRINs.add(network); foundRIN = true;
237    * break; } } } else if (cyObj instanceof CyNetwork) { // if cyObj is a
238    * network, check for residue/chain annotations in an // arbitrary node
239    * CyNetwork rinNet = (CyNetwork) cyObj; if (rinNet.getNodeList().size() > 0)
240    * { specsFound = ChimUtils.getResidueKeys( rinNet.getDefaultNodeTable(),
241    * rinNet.getNodeList().get(0), attrsFound); for (String resSpec : specsFound)
242    * { ChimeraStructuralObject res = ChimUtils.fromAttribute( resSpec,
243    * chimeraManager); if (res != null && (res instanceof ChimeraResidue || res
244    * instanceof ChimeraChain)) { potentialRINs.add(network); foundRIN = true;
245    * break; } } } } } if (foundRIN) { continue; } // if not RIN then associate
246    * new model with the Cytoscape // node if
247    * (!currentChimMap.containsKey(currentModel)) {
248    * currentChimMap.put(currentModel, new HashSet<CyIdentifiable>()); } String
249    * cyObjName = network.getRow(cyObj).get(CyNetwork.NAME, String.class); if
250    * (cyObjName != null && cyObjName.endsWith(currentModel.getModelName())) { //
251    * it is a modbase model, associate directly
252    * currentCyMap.get(cyObj).add(currentModel);
253    * currentChimMap.get(currentModel).add(cyObj);
254    * currentModel.addCyObject(cyObj, network); } else if (specsFound != null &&
255    * specsFound.size() > 0) { for (String resSpec : specsFound) {
256    * ChimeraStructuralObject specModel = ChimUtils.fromAttribute( resSpec,
257    * chimeraManager); if (specModel == null &&
258    * resSpec.equals(currentModel.getModelName())) { specModel =
259    * chimeraManager.getChimeraModel( currentModel.getModelNumber(),
260    * currentModel.getSubModelNumber()); } if (specModel != null &&
261    * currentModel.toSpec().equals(specModel.toSpec()) ||
262    * currentModel.getModelName().equals("smiles:" + resSpec)) {
263    * currentCyMap.get(cyObj).add(currentModel);
264    * currentChimMap.get(currentModel).add(cyObj);
265    * currentModel.addCyObject(cyObj, network);
266    * currentModel.setFuncResidues(ChimUtils.parseFuncRes(
267    * getResidueList(network, cyObj), chimObjName)); } } } } } } } // networks
268    * that contain nodes associated to newly opened models // this will usually
269    * be of length 1 for (CyNetwork net : potentialRINs) {
270    * addStructureNetwork(net); } // update dialog if (mnDialog != null) {
271    * mnDialog.modelChanged(); } aTask.associate(); return true; }
272    */
273   public void closeStructures(Set<String> chimObjNames)
274   {
275     // for each cytoscape object and chimera model pair
276     for (String modelName : chimObjNames)
277     {
278       List<ChimeraModel> models = chimeraManager
279               .getChimeraModels(modelName);
280       for (ChimeraModel model : models)
281       {
282         closeModel(model);
283       }
284     }
285     // if (mnDialog != null) {
286     // mnDialog.modelChanged();
287     // }
288   }
289
290   // TODO: [Optional] Can we make a screenshot of a single molecule?
291   public File saveChimeraImage()
292   {
293     File tmpFile = null;
294     try
295     {
296       // Create the temp file name
297       tmpFile = File.createTempFile("structureViz", ".png");
298       chimeraManager.sendChimeraCommand("set bgTransparency", false);
299       chimeraManager.sendChimeraCommand(
300               "copy file " + tmpFile.getAbsolutePath() + " png", true);
301       chimeraManager.sendChimeraCommand("unset bgTransparency", false);
302     } catch (IOException ioe)
303     {
304       // Log error
305       logger.error("Error writing image", ioe);
306     }
307     return tmpFile;
308   }
309
310   public void closeModel(ChimeraModel model)
311   {
312     // close model in Chimera
313     chimeraManager.closeModel(model);
314     // remove all associations
315     // if (currentChimMap.containsKey(model)) {
316     // for (CyIdentifiable cyObj : model.getCyObjects().keySet()) {
317     // if (cyObj == null) {
318     // continue;
319     // } else if (currentCyMap.containsKey(cyObj)) {
320     // currentCyMap.get(cyObj).remove(model);
321     // } else if (cyObj instanceof CyNetwork) {
322     // for (ChimeraResidue residue : model.getResidues()) {
323     // if (currentChimMap.containsKey(residue)) {
324     // for (CyIdentifiable cyObjRes : currentChimMap.get(residue)) {
325     // if (currentCyMap.containsKey(cyObjRes)) {
326     // currentCyMap.get(cyObjRes).remove(residue);
327     // }
328     // }
329     // currentChimMap.remove(residue);
330     // }
331     // }
332     // }
333     // }
334     // currentChimMap.remove(model);
335     // }
336   }
337
338   // public void addStructureNetwork(CyNetwork rin) {
339   // if (rin == null) {
340   // return;
341   // }
342   // ChimeraModel model = null;
343   // // the network is not added to the model in the currentChimMap
344   // List<String> attrsFound =
345   // CytoUtils.getMatchingAttributes(rin.getDefaultNodeTable(),
346   // getCurrentStructureKeys(rin));
347   // for (CyNode node : rin.getNodeList()) {
348   // if (!networkMap.containsKey(node)) {
349   // networkMap.put(node, new HashSet<CyNetwork>());
350   // }
351   // networkMap.get(node).add(rin);
352   // List<String> specsFound =
353   // ChimUtils.getResidueKeys(rin.getDefaultNodeTable(), node,
354   // attrsFound);
355   // for (String residueSpec : specsFound) {
356   // // if (!rin.getRow(node).isSet(ChimUtils.RESIDUE_ATTR)) {
357   // // continue;
358   // // }
359   // // String residueSpec = rin.getRow(node).get(ChimUtils.RESIDUE_ATTR,
360   // String.class);
361   // ChimeraStructuralObject chimObj = ChimUtils.fromAttribute(residueSpec,
362   // chimeraManager);
363   // // chimObj.getChimeraModel().addCyObject(node, rin);
364   // if (chimObj == null || chimObj instanceof ChimeraModel) {
365   // continue;
366   // }
367   // model = chimObj.getChimeraModel();
368   // if (!currentCyMap.containsKey(node)) {
369   // currentCyMap.put(node, new HashSet<ChimeraStructuralObject>());
370   // }
371   // currentCyMap.get(node).add(chimObj);
372   // if (!currentChimMap.containsKey(chimObj)) {
373   // currentChimMap.put(chimObj, new HashSet<CyIdentifiable>());
374   // }
375   // currentChimMap.get(chimObj).add(node);
376   // }
377   // }
378   // if (model != null) {
379   // model.addCyObject(rin, rin);
380   // if (!currentCyMap.containsKey(rin)) {
381   // currentCyMap.put(rin, new HashSet<ChimeraStructuralObject>());
382   // }
383   // currentCyMap.get(rin).add(model);
384   // }
385   // }
386
387   public void exitChimera()
388   {
389     // // exit chimera, invokes clearOnExitChimera
390     // if (mnDialog != null) {
391     // mnDialog.setVisible(false);
392     // mnDialog = null;
393     // }
394     // if (alDialog != null) {
395     // alDialog.setVisible(false);
396     // }
397     chimeraManager.exitChimera();
398   }
399
400   // invoked by ChimeraManager whenever Chimera exits
401   public void clearOnChimeraExit()
402   {
403     // // clear structures
404     // currentCyMap.clear();
405     // currentChimMap.clear();
406     // networkMap.clear();
407     chimSelectionList.clear();
408     // if (chimTable != null) {
409     // ((CyTableManager)
410     // getService(CyTableManager.class)).deleteTable(chimTable.getSUID());
411     // }
412     // if (mnDialog != null) {
413     // if (mnDialog.isVisible()) {
414     // mnDialog.lostChimera();
415     // mnDialog.setVisible(false);
416     // }
417     // mnDialog = null;
418     // if (alDialog != null) {
419     // alDialog.setVisible(false);
420     // }
421     // }
422   }
423
424   // We need to do this in two passes since some parts of a structure might be
425   // selected and some might not. Our selection model (unfortunately) only
426   // tells
427   // us that something has changed, not what...
428   public void updateCytoscapeSelection()
429   {
430     // List<ChimeraStructuralObject> selectedChimObj
431     ignoreCySelection = true;
432     // System.out.println("update Cytoscape selection");
433     // find all possibly selected Cytoscape objects and unselect them
434     // Set<CyNetwork> networks = new HashSet<CyNetwork>();
435     // for (CyIdentifiable currentCyObj : currentCyMap.keySet()) {
436     // if (!networkMap.containsKey(currentCyObj)) {
437     // continue;
438     // }
439     // Set<CyNetwork> currentCyNetworks = networkMap.get(currentCyObj);
440     // if (currentCyNetworks == null || currentCyNetworks.size() == 0) {
441     //
442     // continue;
443     // }
444     // for (CyNetwork network : currentCyNetworks) {
445     // if ((currentCyObj instanceof CyNode && network.containsNode((CyNode)
446     // currentCyObj))
447     // || (currentCyObj instanceof CyEdge && network
448     // .containsEdge((CyEdge) currentCyObj))) {
449     // network.getRow(currentCyObj).set(CyNetwork.SELECTED, false);
450     // networks.add(network);
451     // }
452     // }
453     // }
454     //
455     // // select only those associated with selected Chimera objects
456     // Set<CyIdentifiable> currentCyObjs = new HashSet<CyIdentifiable>();
457     // for (ChimeraStructuralObject chimObj : chimSelectionList) {
458     // ChimeraModel currentSelModel = chimObj.getChimeraModel();
459     // if (currentChimMap.containsKey(currentSelModel)) {
460     // currentCyObjs.addAll(currentChimMap.get(currentSelModel));
461     // }
462     // if (currentChimMap.containsKey(chimObj)) {
463     // currentCyObjs.addAll(currentChimMap.get(chimObj));
464     // }
465     // // System.out.println(chimObj.toSpec() + ": " +
466     // // currentCyObjs.size());
467     // }
468     // for (CyIdentifiable cyObj : currentCyObjs) {
469     // // System.out.println(cyObj.toString());
470     // if (cyObj == null || !networkMap.containsKey(cyObj)) {
471     // continue;
472     // }
473     // Set<CyNetwork> currentCyNetworks = networkMap.get(cyObj);
474     // if (currentCyNetworks == null || currentCyNetworks.size() == 0) {
475     // continue;
476     // }
477     // for (CyNetwork network : currentCyNetworks) {
478     // if ((cyObj instanceof CyNode && network.containsNode((CyNode) cyObj))
479     // || (cyObj instanceof CyEdge && network.containsEdge((CyEdge) cyObj))) {
480     // network.getRow(cyObj).set(CyNetwork.SELECTED, true);
481     // networks.add(network);
482     // }
483     // }
484     // }
485     //
486     // CyNetworkViewManager cyNetViewManager = (CyNetworkViewManager)
487     // getService(CyNetworkViewManager.class);
488     // // Update network views
489     // for (CyNetwork network : networks) {
490     // Collection<CyNetworkView> views =
491     // cyNetViewManager.getNetworkViews(network);
492     // for (CyNetworkView view : views) {
493     // view.updateView();
494     // }
495     // }
496     ignoreCySelection = false;
497   }
498
499   public void cytoscapeSelectionChanged(Map<Long, Boolean> selectedRows)
500   {
501     // if (ignoreCySelection || currentCyMap.size() == 0) {
502     // return;
503     // }
504     // // clearSelectionList();
505     // // System.out.println("cytoscape selection changed");
506     // // iterate over all cy objects with associated models
507     // for (CyIdentifiable cyObj : currentCyMap.keySet()) {
508     // if (cyObj instanceof CyNetwork ||
509     // !selectedRows.containsKey(cyObj.getSUID())) {
510     // continue;
511     // }
512     // for (ChimeraStructuralObject chimObj : currentCyMap.get(cyObj)) {
513     // if (selectedRows.get(cyObj.getSUID())) {
514     // addChimSelection(chimObj);
515     // if (chimObj instanceof ChimeraResidue) {
516     // if (chimObj.getChimeraModel().isSelected()) {
517     // removeChimSelection(chimObj.getChimeraModel());
518     // } else if (chimObj.getChimeraModel()
519     // .getChain(((ChimeraResidue) chimObj).getChainId()).isSelected()) {
520     // removeChimSelection(chimObj.getChimeraModel().getChain(
521     // ((ChimeraResidue) chimObj).getChainId()));
522     // }
523     // }
524     // } else {
525     // removeChimSelection(chimObj);
526     // if (chimObj.hasSelectedChildren() && chimObj instanceof ChimeraModel) {
527     // for (ChimeraResidue residue : ((ChimeraModel) chimObj)
528     // .getSelectedResidues()) {
529     // removeChimSelection(residue);
530     // }
531     // }
532     // }
533     // }
534     // }
535     // System.out.println("selection list: " + getChimSelectionCount());
536     updateChimeraSelection();
537     selectionChanged();
538   }
539
540   // Save models in a HashMap/Set for better performance?
541   public void updateChimeraSelection()
542   {
543     // System.out.println("update Chimera selection");
544     String selSpec = "";
545     for (int i = 0; i < chimSelectionList.size(); i++)
546     {
547       ChimeraStructuralObject nodeInfo = chimSelectionList.get(i);
548       // we do not care about the model anymore
549       selSpec = selSpec.concat(nodeInfo.toSpec());
550       if (i < chimSelectionList.size() - 1)
551       {
552         selSpec.concat("|");
553       }
554     }
555     if (selSpec.length() > 0)
556     {
557       chimeraManager.select("sel " + selSpec);
558     }
559     else
560     {
561       chimeraManager.select("~sel");
562     }
563   }
564
565   /**
566    * This is called by the selectionListener to let us know that the user has
567    * changed their selection in Chimera. We need to go back to Chimera to find
568    * out what is currently selected and update our list.
569    */
570   public void chimeraSelectionChanged()
571   {
572     // System.out.println("Chimera selection changed");
573     clearSelectionList();
574     // Execute the command to get the list of models with selections
575     Map<Integer, ChimeraModel> selectedModelsMap = chimeraManager
576             .getSelectedModels();
577     // Now get the residue-level data
578     chimeraManager.getSelectedResidues(selectedModelsMap);
579     // Get the selected objects
580     try
581     {
582       for (ChimeraModel selectedModel : selectedModelsMap.values())
583       {
584         int modelNumber = selectedModel.getModelNumber();
585         int subModelNumber = selectedModel.getSubModelNumber();
586         // Get the corresponding "real" model
587         if (chimeraManager.hasChimeraModel(modelNumber, subModelNumber))
588         {
589           ChimeraModel dataModel = chimeraManager
590                   .getChimeraModel(modelNumber, subModelNumber);
591           if (dataModel.getResidueCount() == selectedModel.getResidueCount()
592                   || dataModel
593                           .getModelType() == StructureManager.ModelType.SMILES)
594           {
595             // Select the entire model
596             addChimSelection(dataModel);
597             // dataModel.setSelected(true);
598           }
599           else
600           {
601             for (ChimeraChain selectedChain : selectedModel.getChains())
602             {
603               ChimeraChain dataChain = dataModel
604                       .getChain(selectedChain.getChainId());
605               if (selectedChain.getResidueCount() == dataChain
606                       .getResidueCount())
607               {
608                 addChimSelection(dataChain);
609                 // dataChain.setSelected(true);
610               }
611               // else {
612               // Need to select individual residues
613               for (ChimeraResidue res : selectedChain.getResidues())
614               {
615                 String residueIndex = res.getIndex();
616                 ChimeraResidue residue = dataChain.getResidue(residueIndex);
617                 if (residue == null)
618                 {
619                   continue;
620                 }
621                 addChimSelection(residue);
622                 // residue.setSelected(true);
623               } // resIter.hasNext
624                 // }
625             } // chainIter.hasNext()
626           }
627         }
628       } // modelIter.hasNext()
629     } catch (Exception ex)
630     {
631       logger.warn("Could not update selection", ex);
632     }
633     // System.out.println("selection list: " + getChimSelectionCount());
634     // Finally, update the navigator panel
635     selectionChanged();
636     updateCytoscapeSelection();
637   }
638
639   public void selectFunctResidues(Collection<ChimeraModel> models)
640   {
641     clearSelectionList();
642     for (ChimeraModel model : models)
643     {
644       for (ChimeraResidue residue : model.getFuncResidues())
645       {
646         addChimSelection(residue);
647       }
648     }
649     updateChimeraSelection();
650     updateCytoscapeSelection();
651     selectionChanged();
652   }
653
654   // public void selectFunctResidues(CyNode node, CyNetwork network) {
655   // clearSelectionList();
656   // if (currentCyMap.containsKey(node)) {
657   // Set<ChimeraStructuralObject> chimObjects = currentCyMap.get(node);
658   // for (ChimeraStructuralObject obj : chimObjects) {
659   // if (obj instanceof ChimeraModel) {
660   // ChimeraModel model = (ChimeraModel) obj;
661   // for (ChimeraResidue residue : model.getFuncResidues()) {
662   // addChimSelection(residue);
663   // }
664   // }
665   // }
666   // }
667   // updateChimeraSelection();
668   // updateCytoscapeSelection();
669   // selectionChanged();
670   // }
671
672   public List<ChimeraStructuralObject> getChimSelectionList()
673   {
674     return chimSelectionList;
675   }
676
677   public int getChimSelectionCount()
678   {
679     return chimSelectionList.size();
680   }
681
682   /**
683    * Add a selection to the selection list. This is called primarily by the
684    * Model Navigator Dialog to keep the selections in sync
685    * 
686    * @param selectionToAdd
687    *          the selection to add to our list
688    */
689   public void addChimSelection(ChimeraStructuralObject selectionToAdd)
690   {
691     if (selectionToAdd != null
692             && !chimSelectionList.contains(selectionToAdd))
693     {
694       chimSelectionList.add(selectionToAdd);
695       selectionToAdd.setSelected(true);
696     }
697   }
698
699   /**
700    * Remove a selection from the selection list. This is called primarily by the
701    * Model Navigator Dialog to keep the selections in sync
702    * 
703    * @param selectionToRemove
704    *          the selection to remove from our list
705    */
706   public void removeChimSelection(ChimeraStructuralObject selectionToRemove)
707   {
708     if (selectionToRemove != null
709             && chimSelectionList.contains(selectionToRemove))
710     {
711       chimSelectionList.remove(selectionToRemove);
712       selectionToRemove.setSelected(false);
713     }
714   }
715
716   /**
717    * Clear the list of selected objects
718    */
719   public void clearSelectionList()
720   {
721     for (ChimeraStructuralObject cso : chimSelectionList)
722     {
723       if (cso != null)
724       {
725         cso.setSelected(false);
726       }
727     }
728     chimSelectionList.clear();
729   }
730
731   /**
732    * Associate a new network with the corresponding Chimera objects.
733    * 
734    * @param network
735    */
736
737   /**
738    * Dump and refresh all of our model/chain/residue info
739    */
740   public void updateModels()
741   {
742     // Stop all of our listeners while we try to handle this
743     chimeraManager.stopListening();
744
745     // Get all of the open models
746     List<ChimeraModel> newModelList = chimeraManager.getModelList();
747
748     // Match them up -- assume that the model #'s haven't changed
749     for (ChimeraModel newModel : newModelList)
750     {
751       // Get the color (for our navigator)
752       newModel.setModelColor(chimeraManager.getModelColor(newModel));
753
754       // Get our model info
755       int modelNumber = newModel.getModelNumber();
756       int subModelNumber = newModel.getSubModelNumber();
757
758       // If we already know about this model number, get the Structure,
759       // which tells us about the associated CyNode
760       if (chimeraManager.hasChimeraModel(modelNumber, subModelNumber))
761       {
762         ChimeraModel oldModel = chimeraManager.getChimeraModel(modelNumber,
763                 subModelNumber);
764         chimeraManager.removeChimeraModel(modelNumber, subModelNumber);
765         newModel.setModelType(oldModel.getModelType());
766         if (oldModel.getModelType() == ModelType.SMILES)
767         {
768           newModel.setModelName(oldModel.getModelName());
769         }
770         // re-assign associations to cytoscape objects
771         // Map<CyIdentifiable, CyNetwork> oldModelCyObjs =
772         // oldModel.getCyObjects();
773         // for (CyIdentifiable cyObj : oldModelCyObjs.keySet()) {
774         // // add cy objects to the new model
775         // newModel.addCyObject(cyObj, oldModelCyObjs.get(cyObj));
776         // if (currentCyMap.containsKey(cyObj)) {
777         // currentCyMap.get(cyObj).add(newModel);
778         // if (currentCyMap.get(cyObj).contains(oldModel)) {
779         // currentCyMap.get(cyObj).remove(oldModel);
780         // }
781         // }
782         // }
783         // // add new model to the chimera objects map and remove old model
784         // if (currentChimMap.containsKey(oldModel)) {
785         // currentChimMap.put(newModel, currentChimMap.get(oldModel));
786         // currentChimMap.remove(oldModel);
787         // }
788       }
789       // add new model to ChimeraManager
790       chimeraManager.addChimeraModel(modelNumber, subModelNumber, newModel);
791
792       // Get the residue information
793       if (newModel.getModelType() != ModelType.SMILES)
794       {
795         chimeraManager.addResidues(newModel);
796       }
797       // for (CyIdentifiable cyObj : newModel.getCyObjects().keySet()) {
798       // if (cyObj != null && cyObj instanceof CyNetwork) {
799       // addStructureNetwork((CyNetwork) cyObj);
800       // } else if (cyObj != null && cyObj instanceof CyNode) {
801       // newModel.setFuncResidues(ChimUtils.parseFuncRes(
802       // getResidueList(newModel.getCyObjects().get(cyObj), cyObj),
803       // newModel.getModelName()));
804       // }
805       // }
806     }
807
808     // associate all models with any node or network
809     // aTask.associate();
810
811     // Restart all of our listeners
812     chimeraManager.startListening();
813     // Done
814   }
815
816   public void launchModelNavigatorDialog()
817   {
818     // TODO: [Optional] Use haveGUI flag
819     // if (!haveGUI) {
820     // return;
821     // }
822     // if (mnDialog == null) {
823     // CySwingApplication cyApplication = (CySwingApplication)
824     // getService(CySwingApplication.class);
825     // mnDialog = new ModelNavigatorDialog(cyApplication.getJFrame(), this);
826     // mnDialog.pack();
827     // }
828     // mnDialog.setVisible(true);
829   }
830
831   public boolean isMNDialogOpen()
832   {
833     // if (mnDialog != null && mnDialog.isVisible()) {
834     // return true;
835     // }
836     return false;
837   }
838
839   /**
840    * Invoked by the listener thread.
841    */
842   public void modelChanged()
843   {
844     // if (mnDialog != null) {
845     // mnDialog.modelChanged();
846     // }
847   }
848
849   /**
850    * Inform our interface that the selection has changed
851    */
852   public void selectionChanged()
853   {
854     // if (mnDialog != null) {
855     // // System.out.println("update dialog selection");
856     // mnDialog.updateSelection(new
857     // ArrayList<ChimeraStructuralObject>(chimSelectionList));
858     // }
859   }
860
861   public void launchAlignDialog(boolean useChains)
862   {
863     // TODO: [Optional] Use haveGUI flag
864     // Sometimes it does not appear in Windows
865     // if (!haveGUI) {
866     // return;
867     // }
868     // if (alDialog != null) {
869     // alDialog.setVisible(false);
870     // alDialog.dispose();
871     // }
872     // System.out.println("launch align dialog");
873     List<ChimeraStructuralObject> chimObjectList = new ArrayList<>();
874     for (ChimeraModel model : chimeraManager.getChimeraModels())
875     {
876       if (useChains)
877       {
878         for (ChimeraChain chain : model.getChains())
879         {
880           chimObjectList.add(chain);
881         }
882       }
883       else
884       {
885         chimObjectList.add(model);
886       }
887     }
888     // Bring up the dialog
889     // CySwingApplication cyApplication = (CySwingApplication)
890     // getService(CySwingApplication.class);
891     // alDialog = new AlignStructuresDialog(cyApplication.getJFrame(), this,
892     // chimObjectList);
893     // alDialog.pack();
894     // alDialog.setVisible(true);
895   }
896
897   public List<String> getAllStructureKeys()
898   {
899     return Arrays.asList(defaultStructureKeys);
900   }
901
902   public List<String> getAllChemStructKeys()
903   {
904     return Arrays.asList(defaultChemStructKeys);
905   }
906
907   public List<String> getAllResidueKeys()
908   {
909     return Arrays.asList(defaultResidueKeys);
910   }
911
912   public List<String> getAllChimeraResidueAttributes()
913   {
914     List<String> attributes = new ArrayList<>();
915     // attributes.addAll(rinManager.getResAttrs());
916     attributes.addAll(chimeraManager.getAttrList());
917     return attributes;
918   }
919
920   StructureSettings defaultSettings = null;
921
922   // TODO: [Optional] Change priority of Chimera paths
923   public static List<String> getChimeraPaths(boolean isChimeraX)
924   {
925     List<String> pathList = new ArrayList<>();
926
927     // if no network is available and the settings have been modified by the
928     // user, check for a
929     // path to chimera
930     //
931     // For Jalview, Preferences/Cache plays this role instead
932     // if (defaultSettings != null)
933     // {
934     // String defaultPath = defaultSettings.getChimeraPath();
935     // if (defaultPath != null && !defaultPath.equals(""))
936     // {
937     // pathList.add(defaultPath);
938     // return pathList;
939     // }
940     // }
941
942     String os = System.getProperty("os.name");
943     String userPath = Cache
944             .getDefault(isChimeraX ? Preferences.CHIMERAX_PATH
945                     : Preferences.CHIMERA_PATH, null);
946
947     /*
948      * paths are based on getChimeraPaths() in
949      * Chimera:
950      * https://github.com/RBVI/structureViz2/blob/master/src/main/java/edu/ucsf/rbvi/structureViz2/internal/model/StructureManager.java
951      * ChimeraX:
952      * https://github.com/RBVI/structureVizX/blob/master/src/main/java/edu/ucsf/rbvi/structureVizX/internal/model/StructureManager.java
953      */
954     String chimera = isChimeraX ? "ChimeraX" : "Chimera";
955     String chimeraExe = isChimeraX ? "ChimeraX" : "chimera";
956
957     /*
958      * Jalview addition: check if path set in user preferences
959      */
960     if (userPath != null)
961     {
962       // in macos, deal with the user selecting the .app folder
963       boolean adjusted = false;
964       if (os.startsWith("Mac") && userPath.endsWith((".app")))
965       {
966         String possiblePath = String.format("%s/Contents/MacOS/%s",
967                 userPath, chimeraExe);
968         if (new File(possiblePath).exists())
969         {
970           pathList.add(possiblePath);
971           adjusted = true;
972         }
973       }
974       if (!adjusted)
975       {
976         pathList.add(userPath);
977       }
978     }
979
980     // Add default installation paths
981     if (os.startsWith("Linux"))
982     {
983       // ChimeraX .deb and .rpm packages put symbolic link from
984       // /usr/bin/chimerax
985       pathList.add(String.format("/usr/bin/%s",
986               chimeraExe.toLowerCase(Locale.ROOT)));
987       pathList.add(String.format("/usr/bin/%s", chimeraExe));
988
989       pathList.add(String.format("/usr/local/bin/%s",
990               chimeraExe.toLowerCase(Locale.ROOT)));
991       pathList.add(String.format("/usr/local/bin/%s", chimeraExe));
992
993       // these paths also used by .deb and .rpm
994       pathList.add(String.format("/usr/lib/ucsf-%s/bin/%s",
995               chimera.toLowerCase(Locale.ROOT), chimeraExe));
996       pathList.add(String.format("/usr/libexec/UCSF-%s/bin/%s", chimera,
997               chimeraExe));
998
999       pathList.add(String.format("/usr/local/chimera/bin/%s", chimeraExe));
1000
1001       // user home paths
1002       pathList.add(
1003               String.format("%s/bin/%s", System.getProperty("user.home"),
1004                       chimeraExe.toLowerCase(Locale.ROOT)));
1005       pathList.add(String.format("%s/bin/%s",
1006               System.getProperty("user.home"), chimeraExe));
1007       pathList.add(String.format("%s/opt/bin/%s",
1008               System.getProperty("user.home"),
1009               chimeraExe.toLowerCase(Locale.ROOT)));
1010       pathList.add(String.format("%s/opt/bin/%s",
1011               System.getProperty("user.home"), chimeraExe));
1012       pathList.add(String.format("%s/local/bin/%s",
1013               System.getProperty("user.home"),
1014               chimeraExe.toLowerCase(Locale.ROOT)));
1015       pathList.add(String.format("%s/local/bin/%s",
1016               System.getProperty("user.home"), chimeraExe));
1017     }
1018     else if (os.startsWith("Windows"))
1019     {
1020       for (String root : new String[] { "\\Program Files",
1021           "C:\\Program Files", "\\Program Files (x86)",
1022           "C:\\Program Files (x86)", String.format("%s\\AppData\\Local",
1023                   System.getProperty("user.home")) })
1024       {
1025         String[] candidates = isChimeraX ? CHIMERAX_VERSIONS
1026                 : CHIMERA_VERSIONS;
1027         for (String version : candidates)
1028         {
1029           // TODO original code doesn't include version in path; which is right?
1030           String path = String.format("%s\\%s %s\\bin\\%s", root, chimera,
1031                   version, chimeraExe);
1032           pathList.add(path);
1033           pathList.add(path + ".exe");
1034         }
1035         // try without a version number too
1036         String path = String.format("%s\\%s\\bin\\%s", root, chimera,
1037                 chimeraExe);
1038         pathList.add(path);
1039         pathList.add(path + ".exe");
1040       }
1041     }
1042     else if (os.startsWith("Mac"))
1043     {
1044       // check for installations with version numbers first
1045       String[] candidates = isChimeraX ? CHIMERAX_VERSIONS
1046               : CHIMERA_VERSIONS;
1047       for (String version : candidates)
1048       {
1049         pathList.add(
1050                 String.format("/Applications/%s-%s.app/Contents/MacOS/%s",
1051                         chimera, version, chimeraExe));
1052         pathList.add(
1053                 String.format("%s/Applications/%s-%s.app/Contents/MacOS/%s",
1054                         System.getProperty("user.home"), chimera, version,
1055                         chimeraExe));
1056       }
1057       pathList.add(String.format("/Applications/%s.app/Contents/MacOS/%s",
1058               chimera, chimeraExe));
1059       pathList.add(String.format("%s/Applications/%s.app/Contents/MacOS/%s",
1060               System.getProperty("user.home"), chimera, chimeraExe));
1061     }
1062     return pathList;
1063   }
1064
1065   public void setChimeraPathProperty(String path)
1066   {
1067     // CytoUtils.setDefaultChimeraPath(registrar, chimeraPropertyName,
1068     // chimeraPathPropertyKey,
1069     // path);
1070   }
1071
1072   public void setStructureSettings(StructureSettings structureSettings)
1073   {
1074     this.defaultSettings = structureSettings;
1075   }
1076
1077   public String getCurrentChimeraPath(Object object)
1078   {
1079     if (defaultSettings != null)
1080     {
1081       return defaultSettings.getChimeraPath();
1082     }
1083     else
1084     {
1085       return "";
1086     }
1087   }
1088
1089   // public void initChimTable() {
1090   // CyTableManager manager = (CyTableManager) getService(CyTableManager.class);
1091   // CyTableFactory factory = (CyTableFactory) getService(CyTableFactory.class);
1092   // for (CyTable table : manager.getGlobalTables()) {
1093   // if (table.getTitle().equals(chimeraOutputTable)) {
1094   // manager.deleteTable(table.getSUID());
1095   // }
1096   // }
1097   // chimTable = factory.createTable(chimeraOutputTable, chimeraCommandAttr,
1098   // String.class,
1099   // false, true);
1100   // manager.addTable(chimTable);
1101   // if (chimTable.getColumn(chimeraOutputAttr) == null) {
1102   // chimTable.createListColumn(chimeraOutputAttr, String.class, false);
1103   // }
1104   // }
1105
1106   // public void addChimReply(String command, List<String> reply) {
1107   // chimTable.getRow(command).set(chimeraOutputAttr, reply);
1108   // }
1109
1110 }