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