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