Clover icon

jalviewX

  1. Project Clover database Wed Oct 31 2018 15:13:58 GMT
  2. Package jalview.ext.rbvi.chimera

File JalviewChimeraBinding.java

 

Coverage histogram

../../../../img/srcFileCovDistChart0.png
56% of files have more coverage

Code metrics

128
343
44
1
1,311
806
125
0.36
7.8
44
2.84

Classes

Class Line # Actions
JalviewChimeraBinding 63 343 125 515
0.00%
 

Contributing tests

No tests hitting this source file were found.

Source view

1    /*
2    * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3    * Copyright (C) $$Year-Rel$$ The Jalview Authors
4    *
5    * This file is part of Jalview.
6    *
7    * Jalview is free software: you can redistribute it and/or
8    * modify it under the terms of the GNU General Public License
9    * as published by the Free Software Foundation, either version 3
10    * of the License, or (at your option) any later version.
11    *
12    * Jalview is distributed in the hope that it will be useful, but
13    * WITHOUT ANY WARRANTY; without even the implied warranty
14    * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15    * PURPOSE. See the GNU General Public License for more details.
16    *
17    * You should have received a copy of the GNU General Public License
18    * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19    * The Jalview Authors are detailed in the 'AUTHORS' file.
20    */
21    package jalview.ext.rbvi.chimera;
22   
23    import jalview.api.AlignmentViewPanel;
24    import jalview.api.SequenceRenderer;
25    import jalview.api.structures.JalviewStructureDisplayI;
26    import jalview.bin.Cache;
27    import jalview.datamodel.AlignmentI;
28    import jalview.datamodel.HiddenColumns;
29    import jalview.datamodel.PDBEntry;
30    import jalview.datamodel.SearchResultMatchI;
31    import jalview.datamodel.SearchResultsI;
32    import jalview.datamodel.SequenceFeature;
33    import jalview.datamodel.SequenceI;
34    import jalview.httpserver.AbstractRequestHandler;
35    import jalview.io.DataSourceType;
36    import jalview.schemes.ColourSchemeI;
37    import jalview.schemes.ResidueProperties;
38    import jalview.structure.AtomSpec;
39    import jalview.structure.StructureMappingcommandSet;
40    import jalview.structure.StructureSelectionManager;
41    import jalview.structures.models.AAStructureBindingModel;
42    import jalview.util.MessageManager;
43   
44    import java.awt.Color;
45    import java.io.File;
46    import java.io.FileOutputStream;
47    import java.io.IOException;
48    import java.io.PrintWriter;
49    import java.net.BindException;
50    import java.util.ArrayList;
51    import java.util.BitSet;
52    import java.util.Collections;
53    import java.util.Hashtable;
54    import java.util.LinkedHashMap;
55    import java.util.List;
56    import java.util.Map;
57   
58    import ext.edu.ucsf.rbvi.strucviz2.ChimeraManager;
59    import ext.edu.ucsf.rbvi.strucviz2.ChimeraModel;
60    import ext.edu.ucsf.rbvi.strucviz2.StructureManager;
61    import ext.edu.ucsf.rbvi.strucviz2.StructureManager.ModelType;
62   
 
63    public abstract class JalviewChimeraBinding extends AAStructureBindingModel
64    {
65    public static final String CHIMERA_FEATURE_GROUP = "Chimera";
66   
67    // Chimera clause to exclude alternate locations in atom selection
68    private static final String NO_ALTLOCS = "&~@.B-Z&~@.2-9";
69   
70    private static final String COLOURING_CHIMERA = MessageManager
71    .getString("status.colouring_chimera");
72   
73    private static final boolean debug = false;
74   
75    private static final String PHOSPHORUS = "P";
76   
77    private static final String ALPHACARBON = "CA";
78   
79    private List<String> chainNames = new ArrayList<String>();
80   
81    private Hashtable<String, String> chainFile = new Hashtable<String, String>();
82   
83    /*
84    * Object through which we talk to Chimera
85    */
86    private ChimeraManager viewer;
87   
88    /*
89    * Object which listens to Chimera notifications
90    */
91    private AbstractRequestHandler chimeraListener;
92   
93    /*
94    * set if chimera state is being restored from some source - instructs binding
95    * not to apply default display style when structure set is updated for first
96    * time.
97    */
98    private boolean loadingFromArchive = false;
99   
100    /*
101    * flag to indicate if the Chimera viewer should ignore sequence colouring
102    * events from the structure manager because the GUI is still setting up
103    */
104    private boolean loadingFinished = true;
105   
106    /*
107    * Map of ChimeraModel objects keyed by PDB full local file name
108    */
109    private Map<String, List<ChimeraModel>> chimeraMaps = new LinkedHashMap<String, List<ChimeraModel>>();
110   
111    String lastHighlightCommand;
112   
113    /*
114    * incremented every time a load notification is successfully handled -
115    * lightweight mechanism for other threads to detect when they can start
116    * referring to new structures.
117    */
118    private long loadNotifiesHandled = 0;
119   
120    private Thread chimeraMonitor;
121   
122    /**
123    * Open a PDB structure file in Chimera and set up mappings from Jalview.
124    *
125    * We check if the PDB model id is already loaded in Chimera, if so don't
126    * reopen it. This is the case if Chimera has opened a saved session file.
127    *
128    * @param pe
129    * @return
130    */
 
131  0 toggle public boolean openFile(PDBEntry pe)
132    {
133  0 String file = pe.getFile();
134  0 try
135    {
136  0 List<ChimeraModel> modelsToMap = new ArrayList<ChimeraModel>();
137  0 List<ChimeraModel> oldList = viewer.getModelList();
138  0 boolean alreadyOpen = false;
139   
140    /*
141    * If Chimera already has this model, don't reopen it, but do remap it.
142    */
143  0 for (ChimeraModel open : oldList)
144    {
145  0 if (open.getModelName().equals(pe.getId()))
146    {
147  0 alreadyOpen = true;
148  0 modelsToMap.add(open);
149    }
150    }
151   
152    /*
153    * If Chimera doesn't yet have this model, ask it to open it, and retrieve
154    * the model name(s) added by Chimera.
155    */
156  0 if (!alreadyOpen)
157    {
158  0 viewer.openModel(file, pe.getId(), ModelType.PDB_MODEL);
159  0 List<ChimeraModel> newList = viewer.getModelList();
160    // JAL-1728 newList.removeAll(oldList) does not work
161  0 for (ChimeraModel cm : newList)
162    {
163  0 if (cm.getModelName().equals(pe.getId()))
164    {
165  0 modelsToMap.add(cm);
166    }
167    }
168    }
169   
170  0 chimeraMaps.put(file, modelsToMap);
171   
172  0 if (getSsm() != null)
173    {
174  0 getSsm().addStructureViewerListener(this);
175    }
176  0 return true;
177    } catch (Exception q)
178    {
179  0 log("Exception when trying to open model " + file + "\n"
180    + q.toString());
181  0 q.printStackTrace();
182    }
183  0 return false;
184    }
185   
186    /**
187    * Constructor
188    *
189    * @param ssm
190    * @param pdbentry
191    * @param sequenceIs
192    * @param protocol
193    */
 
194  0 toggle public JalviewChimeraBinding(StructureSelectionManager ssm,
195    PDBEntry[] pdbentry, SequenceI[][] sequenceIs,
196    DataSourceType protocol)
197    {
198  0 super(ssm, pdbentry, sequenceIs, protocol);
199  0 viewer = new ChimeraManager(new StructureManager(true));
200    }
201   
202    /**
203    * Starts a thread that waits for the Chimera process to finish, so that we
204    * can then close the associated resources. This avoids leaving orphaned
205    * Chimera viewer panels in Jalview if the user closes Chimera.
206    */
 
207  0 toggle protected void startChimeraProcessMonitor()
208    {
209  0 final Process p = viewer.getChimeraProcess();
210  0 chimeraMonitor = new Thread(new Runnable()
211    {
212   
 
213  0 toggle @Override
214    public void run()
215    {
216  0 try
217    {
218  0 p.waitFor();
219  0 JalviewStructureDisplayI display = getViewer();
220  0 if (display != null)
221    {
222  0 display.closeViewer(false);
223    }
224    } catch (InterruptedException e)
225    {
226    // exit thread if Chimera Viewer is closed in Jalview
227    }
228    }
229    });
230  0 chimeraMonitor.start();
231    }
232   
233    /**
234    * Start a dedicated HttpServer to listen for Chimera notifications, and tell
235    * it to start listening
236    */
 
237  0 toggle public void startChimeraListener()
238    {
239  0 try
240    {
241  0 chimeraListener = new ChimeraListener(this);
242  0 viewer.startListening(chimeraListener.getUri());
243    } catch (BindException e)
244    {
245  0 System.err.println(
246    "Failed to start Chimera listener: " + e.getMessage());
247    }
248    }
249   
250    /**
251    * Tells Chimera to display only the specified chains
252    *
253    * @param toshow
254    */
 
255  0 toggle public void showChains(List<String> toshow)
256    {
257    /*
258    * Construct a chimera command like
259    *
260    * ~display #*;~ribbon #*;ribbon :.A,:.B
261    */
262  0 StringBuilder cmd = new StringBuilder(64);
263  0 boolean first = true;
264  0 for (String chain : toshow)
265    {
266  0 int modelNumber = getModelNoForChain(chain);
267  0 String showChainCmd = modelNumber == -1 ? ""
268    : modelNumber + ":." + chain.split(":")[1];
269  0 if (!first)
270    {
271  0 cmd.append(",");
272    }
273  0 cmd.append(showChainCmd);
274  0 first = false;
275    }
276   
277    /*
278    * could append ";focus" to this command to resize the display to fill the
279    * window, but it looks more helpful not to (easier to relate chains to the
280    * whole)
281    */
282  0 final String command = "~display #*; ~ribbon #*; ribbon :"
283    + cmd.toString();
284  0 sendChimeraCommand(command, false);
285    }
286   
287    /**
288    * Close down the Jalview viewer and listener, and (optionally) the associated
289    * Chimera window.
290    */
 
291  0 toggle public void closeViewer(boolean closeChimera)
292    {
293  0 getSsm().removeStructureViewerListener(this, this.getStructureFiles());
294  0 if (closeChimera)
295    {
296  0 viewer.exitChimera();
297    }
298  0 if (this.chimeraListener != null)
299    {
300  0 chimeraListener.shutdown();
301  0 chimeraListener = null;
302    }
303  0 viewer = null;
304   
305  0 if (chimeraMonitor != null)
306    {
307  0 chimeraMonitor.interrupt();
308    }
309  0 releaseUIResources();
310    }
311   
 
312  0 toggle @Override
313    public void colourByChain()
314    {
315  0 colourBySequence = false;
316  0 sendAsynchronousCommand("rainbow chain", COLOURING_CHIMERA);
317    }
318   
319    /**
320    * Constructs and sends a Chimera command to colour by charge
321    * <ul>
322    * <li>Aspartic acid and Glutamic acid (negative charge) red</li>
323    * <li>Lysine and Arginine (positive charge) blue</li>
324    * <li>Cysteine - yellow</li>
325    * <li>all others - white</li>
326    * </ul>
327    */
 
328  0 toggle @Override
329    public void colourByCharge()
330    {
331  0 colourBySequence = false;
332  0 String command = "color white;color red ::ASP;color red ::GLU;color blue ::LYS;color blue ::ARG;color yellow ::CYS";
333  0 sendAsynchronousCommand(command, COLOURING_CHIMERA);
334    }
335   
336    /**
337    * {@inheritDoc}
338    */
 
339  0 toggle @Override
340    public String superposeStructures(AlignmentI[] _alignment,
341    int[] _refStructure, HiddenColumns[] _hiddenCols)
342    {
343  0 StringBuilder allComs = new StringBuilder(128);
344  0 String[] files = getStructureFiles();
345   
346  0 if (!waitForFileLoad(files))
347    {
348  0 return null;
349    }
350   
351  0 refreshPdbEntries();
352  0 StringBuilder selectioncom = new StringBuilder(256);
353  0 for (int a = 0; a < _alignment.length; a++)
354    {
355  0 int refStructure = _refStructure[a];
356  0 AlignmentI alignment = _alignment[a];
357  0 HiddenColumns hiddenCols = _hiddenCols[a];
358   
359  0 if (refStructure >= files.length)
360    {
361  0 System.err.println("Ignoring invalid reference structure value "
362    + refStructure);
363  0 refStructure = -1;
364    }
365   
366    /*
367    * 'matched' bit i will be set for visible alignment columns i where
368    * all sequences have a residue with a mapping to the PDB structure
369    */
370  0 BitSet matched = new BitSet();
371  0 for (int m = 0; m < alignment.getWidth(); m++)
372    {
373  0 if (hiddenCols == null || hiddenCols.isVisible(m))
374    {
375  0 matched.set(m);
376    }
377    }
378   
379  0 SuperposeData[] structures = new SuperposeData[files.length];
380  0 for (int f = 0; f < files.length; f++)
381    {
382  0 structures[f] = new SuperposeData(alignment.getWidth());
383    }
384   
385    /*
386    * Calculate the superposable alignment columns ('matched'), and the
387    * corresponding structure residue positions (structures.pdbResNo)
388    */
389  0 int candidateRefStructure = findSuperposableResidues(alignment,
390    matched, structures);
391  0 if (refStructure < 0)
392    {
393    /*
394    * If no reference structure was specified, pick the first one that has
395    * a mapping in the alignment
396    */
397  0 refStructure = candidateRefStructure;
398    }
399   
400  0 int nmatched = matched.cardinality();
401  0 if (nmatched < 4)
402    {
403  0 return MessageManager.formatMessage("label.insufficient_residues",
404    nmatched);
405    }
406   
407    /*
408    * Generate select statements to select regions to superimpose structures
409    */
410  0 String[] selcom = new String[files.length];
411  0 for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
412    {
413  0 String chainCd = "." + structures[pdbfnum].chain;
414  0 int lpos = -1;
415  0 boolean run = false;
416  0 StringBuilder molsel = new StringBuilder();
417   
418  0 int nextColumnMatch = matched.nextSetBit(0);
419  0 while (nextColumnMatch != -1)
420    {
421  0 int pdbResNum = structures[pdbfnum].pdbResNo[nextColumnMatch];
422  0 if (lpos != pdbResNum - 1)
423    {
424    /*
425    * discontiguous - append last residue now
426    */
427  0 if (lpos != -1)
428    {
429  0 molsel.append(String.valueOf(lpos));
430  0 molsel.append(chainCd);
431  0 molsel.append(",");
432    }
433  0 run = false;
434    }
435    else
436    {
437    /*
438    * extending a contiguous run
439    */
440  0 if (!run)
441    {
442    /*
443    * start the range selection
444    */
445  0 molsel.append(String.valueOf(lpos));
446  0 molsel.append("-");
447    }
448  0 run = true;
449    }
450  0 lpos = pdbResNum;
451  0 nextColumnMatch = matched.nextSetBit(nextColumnMatch + 1);
452    }
453   
454    /*
455    * and terminate final selection
456    */
457  0 if (lpos != -1)
458    {
459  0 molsel.append(String.valueOf(lpos));
460  0 molsel.append(chainCd);
461    }
462  0 if (molsel.length() > 1)
463    {
464  0 selcom[pdbfnum] = molsel.toString();
465  0 selectioncom.append("#").append(String.valueOf(pdbfnum))
466    .append(":");
467  0 selectioncom.append(selcom[pdbfnum]);
468  0 selectioncom.append(" ");
469  0 if (pdbfnum < files.length - 1)
470    {
471  0 selectioncom.append("| ");
472    }
473    }
474    else
475    {
476  0 selcom[pdbfnum] = null;
477    }
478    }
479   
480  0 StringBuilder command = new StringBuilder(256);
481  0 for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
482    {
483  0 if (pdbfnum == refStructure || selcom[pdbfnum] == null
484    || selcom[refStructure] == null)
485    {
486  0 continue;
487    }
488  0 if (command.length() > 0)
489    {
490  0 command.append(";");
491    }
492   
493    /*
494    * Form Chimera match command, from the 'new' structure to the
495    * 'reference' structure e.g. (50 residues, chain B/A, alphacarbons):
496    *
497    * match #1:1-30.B,81-100.B@CA #0:21-40.A,61-90.A@CA
498    *
499    * @see
500    * https://www.cgl.ucsf.edu/chimera/docs/UsersGuide/midas/match.html
501    */
502  0 command.append("match ").append(getModelSpec(pdbfnum)).append(":");
503  0 command.append(selcom[pdbfnum]);
504  0 command.append("@").append(
505  0 structures[pdbfnum].isRna ? PHOSPHORUS : ALPHACARBON);
506    // JAL-1757 exclude alternate CA locations
507  0 command.append(NO_ALTLOCS);
508  0 command.append(" ").append(getModelSpec(refStructure)).append(":");
509  0 command.append(selcom[refStructure]);
510  0 command.append("@").append(
511  0 structures[refStructure].isRna ? PHOSPHORUS : ALPHACARBON);
512  0 command.append(NO_ALTLOCS);
513    }
514  0 if (selectioncom.length() > 0)
515    {
516  0 if (debug)
517    {
518  0 System.out.println("Select regions:\n" + selectioncom.toString());
519  0 System.out.println(
520    "Superimpose command(s):\n" + command.toString());
521    }
522  0 allComs.append("~display all; chain @CA|P; ribbon ")
523    .append(selectioncom.toString())
524    .append(";" + command.toString());
525    }
526    }
527   
528  0 String error = null;
529  0 if (selectioncom.length() > 0)
530    {
531    // TODO: visually distinguish regions that were superposed
532  0 if (selectioncom.substring(selectioncom.length() - 1).equals("|"))
533    {
534  0 selectioncom.setLength(selectioncom.length() - 1);
535    }
536  0 if (debug)
537    {
538  0 System.out.println("Select regions:\n" + selectioncom.toString());
539    }
540  0 allComs.append("; ~display all; chain @CA|P; ribbon ")
541    .append(selectioncom.toString()).append("; focus");
542  0 List<String> chimeraReplies = sendChimeraCommand(allComs.toString(),
543    true);
544  0 for (String reply : chimeraReplies)
545    {
546  0 if (reply.toLowerCase().contains("unequal numbers of atoms"))
547    {
548  0 error = reply;
549    }
550    }
551    }
552  0 return error;
553    }
554   
555    /**
556    * Helper method to construct model spec in Chimera format:
557    * <ul>
558    * <li>#0 (#1 etc) for a PDB file with no sub-models</li>
559    * <li>#0.1 (#1.1 etc) for a PDB file with sub-models</li>
560    * <ul>
561    * Note for now we only ever choose the first of multiple models. This
562    * corresponds to the hard-coded Jmol equivalent (compare {1.1}). Refactor in
563    * future if there is a need to select specific sub-models.
564    *
565    * @param pdbfnum
566    * @return
567    */
 
568  0 toggle protected String getModelSpec(int pdbfnum)
569    {
570  0 if (pdbfnum < 0 || pdbfnum >= getPdbCount())
571    {
572  0 return "";
573    }
574   
575    /*
576    * For now, the test for having sub-models is whether multiple Chimera
577    * models are mapped for the PDB file; the models are returned as a response
578    * to the Chimera command 'list models type molecule', see
579    * ChimeraManager.getModelList().
580    */
581  0 List<ChimeraModel> maps = chimeraMaps.get(getStructureFiles()[pdbfnum]);
582  0 boolean hasSubModels = maps != null && maps.size() > 1;
583  0 return "#" + String.valueOf(pdbfnum) + (hasSubModels ? ".1" : "");
584    }
585   
586    /**
587    * Launch Chimera, unless an instance linked to this object is already
588    * running. Returns true if Chimera is successfully launched, or already
589    * running, else false.
590    *
591    * @return
592    */
 
593  0 toggle public boolean launchChimera()
594    {
595  0 if (viewer.isChimeraLaunched())
596    {
597  0 return true;
598    }
599   
600  0 boolean launched = viewer
601    .launchChimera(StructureManager.getChimeraPaths());
602  0 if (launched)
603    {
604  0 startChimeraProcessMonitor();
605    }
606    else
607    {
608  0 log("Failed to launch Chimera!");
609    }
610  0 return launched;
611    }
612   
613    /**
614    * Answers true if the Chimera process is still running, false if ended or not
615    * started.
616    *
617    * @return
618    */
 
619  0 toggle public boolean isChimeraRunning()
620    {
621  0 return viewer.isChimeraLaunched();
622    }
623   
624    /**
625    * Send a command to Chimera, and optionally log and return any responses.
626    * <p>
627    * Does nothing, and returns null, if the command is the same as the last one
628    * sent [why?].
629    *
630    * @param command
631    * @param getResponse
632    */
 
633  0 toggle public List<String> sendChimeraCommand(final String command,
634    boolean getResponse)
635    {
636  0 if (viewer == null)
637    {
638    // ? thread running after viewer shut down
639  0 return null;
640    }
641  0 List<String> reply = null;
642  0 viewerCommandHistory(false);
643  0 if (true /*lastCommand == null || !lastCommand.equals(command)*/)
644    {
645    // trim command or it may never find a match in the replyLog!!
646  0 List<String> lastReply = viewer.sendChimeraCommand(command.trim(),
647    getResponse);
648  0 if (getResponse)
649    {
650  0 reply = lastReply;
651  0 if (debug)
652    {
653  0 log("Response from command ('" + command + "') was:\n"
654    + lastReply);
655    }
656    }
657    }
658  0 viewerCommandHistory(true);
659   
660  0 return reply;
661    }
662   
663    /**
664    * Send a Chimera command asynchronously in a new thread. If the progress
665    * message is not null, display this message while the command is executing.
666    *
667    * @param command
668    * @param progressMsg
669    */
670    protected abstract void sendAsynchronousCommand(String command,
671    String progressMsg);
672   
673    /**
674    * Sends a set of colour commands to the structure viewer
675    *
676    * @param colourBySequenceCommands
677    */
 
678  0 toggle @Override
679    protected void colourBySequence(
680    StructureMappingcommandSet[] colourBySequenceCommands)
681    {
682  0 for (StructureMappingcommandSet cpdbbyseq : colourBySequenceCommands)
683    {
684  0 for (String command : cpdbbyseq.commands)
685    {
686  0 sendAsynchronousCommand(command, COLOURING_CHIMERA);
687    }
688    }
689    }
690   
691    /**
692    * @param files
693    * @param sr
694    * @param viewPanel
695    * @return
696    */
 
697  0 toggle @Override
698    protected StructureMappingcommandSet[] getColourBySequenceCommands(
699    String[] files, SequenceRenderer sr, AlignmentViewPanel viewPanel)
700    {
701  0 return ChimeraCommands.getColourBySequenceCommand(getSsm(), files,
702    getSequence(), sr, viewPanel);
703    }
704   
705    /**
706    * @param command
707    */
 
708  0 toggle protected void executeWhenReady(String command)
709    {
710  0 waitForChimera();
711  0 sendChimeraCommand(command, false);
712  0 waitForChimera();
713    }
714   
 
715  0 toggle private void waitForChimera()
716    {
717  0 while (viewer != null && viewer.isBusy())
718    {
719  0 try
720    {
721  0 Thread.sleep(15);
722    } catch (InterruptedException q)
723    {
724    }
725    }
726    }
727   
728    // End StructureListener
729    // //////////////////////////
730   
731    /**
732    * instruct the Jalview binding to update the pdbentries vector if necessary
733    * prior to matching the viewer's contents to the list of structure files
734    * Jalview knows about.
735    */
736    public abstract void refreshPdbEntries();
737   
738    /**
739    * map between index of model filename returned from getPdbFile and the first
740    * index of models from this file in the viewer. Note - this is not trimmed -
741    * use getPdbFile to get number of unique models.
742    */
743    private int _modelFileNameMap[];
744   
745    // ////////////////////////////////
746    // /StructureListener
 
747  0 toggle @Override
748    public synchronized String[] getStructureFiles()
749    {
750  0 if (viewer == null)
751    {
752  0 return new String[0];
753    }
754   
755  0 return chimeraMaps.keySet()
756    .toArray(modelFileNames = new String[chimeraMaps.size()]);
757    }
758   
759    /**
760    * Construct and send a command to highlight zero, one or more atoms. We do
761    * this by sending an "rlabel" command to show the residue label at that
762    * position.
763    */
 
764  0 toggle @Override
765    public void highlightAtoms(List<AtomSpec> atoms)
766    {
767  0 if (atoms == null || atoms.size() == 0)
768    {
769  0 return;
770    }
771   
772  0 StringBuilder cmd = new StringBuilder(128);
773  0 boolean first = true;
774  0 boolean found = false;
775   
776  0 for (AtomSpec atom : atoms)
777    {
778  0 int pdbResNum = atom.getPdbResNum();
779  0 String chain = atom.getChain();
780  0 String pdbfile = atom.getPdbFile();
781  0 List<ChimeraModel> cms = chimeraMaps.get(pdbfile);
782  0 if (cms != null && !cms.isEmpty())
783    {
784  0 if (first)
785    {
786  0 cmd.append("rlabel #").append(cms.get(0).getModelNumber())
787    .append(":");
788    }
789    else
790    {
791  0 cmd.append(",");
792    }
793  0 first = false;
794  0 cmd.append(pdbResNum);
795  0 if (!chain.equals(" "))
796    {
797  0 cmd.append(".").append(chain);
798    }
799  0 found = true;
800    }
801    }
802  0 String command = cmd.toString();
803   
804    /*
805    * avoid repeated commands for the same residue
806    */
807  0 if (command.equals(lastHighlightCommand))
808    {
809  0 return;
810    }
811   
812    /*
813    * unshow the label for the previous residue
814    */
815  0 if (lastHighlightCommand != null)
816    {
817  0 viewer.sendChimeraCommand("~" + lastHighlightCommand, false);
818    }
819  0 if (found)
820    {
821  0 viewer.sendChimeraCommand(command, false);
822    }
823  0 this.lastHighlightCommand = command;
824    }
825   
826    /**
827    * Query Chimera for its current selection, and highlight it on the alignment
828    */
 
829  0 toggle public void highlightChimeraSelection()
830    {
831    /*
832    * Ask Chimera for its current selection
833    */
834  0 List<String> selection = viewer.getSelectedResidueSpecs();
835   
836    /*
837    * Parse model number, residue and chain for each selected position,
838    * formatted as #0:123.A or #1.2:87.B (#model.submodel:residue.chain)
839    */
840  0 List<AtomSpec> atomSpecs = convertStructureResiduesToAlignment(
841    selection);
842   
843    /*
844    * Broadcast the selection (which may be empty, if the user just cleared all
845    * selections)
846    */
847  0 getSsm().mouseOverStructure(atomSpecs);
848    }
849   
850    /**
851    * Converts a list of Chimera atomspecs to a list of AtomSpec representing the
852    * corresponding residues (if any) in Jalview
853    *
854    * @param structureSelection
855    * @return
856    */
 
857  0 toggle protected List<AtomSpec> convertStructureResiduesToAlignment(
858    List<String> structureSelection)
859    {
860  0 List<AtomSpec> atomSpecs = new ArrayList<AtomSpec>();
861  0 for (String atomSpec : structureSelection)
862    {
863  0 try
864    {
865  0 AtomSpec spec = AtomSpec.fromChimeraAtomspec(atomSpec);
866  0 String pdbfilename = getPdbFileForModel(spec.getModelNumber());
867  0 spec.setPdbFile(pdbfilename);
868  0 atomSpecs.add(spec);
869    } catch (IllegalArgumentException e)
870    {
871  0 System.err.println("Failed to parse atomspec: " + atomSpec);
872    }
873    }
874  0 return atomSpecs;
875    }
876   
877    /**
878    * @param modelId
879    * @return
880    */
 
881  0 toggle protected String getPdbFileForModel(int modelId)
882    {
883    /*
884    * Work out the pdbfilename from the model number
885    */
886  0 String pdbfilename = modelFileNames[0];
887  0 findfileloop: for (String pdbfile : this.chimeraMaps.keySet())
888    {
889  0 for (ChimeraModel cm : chimeraMaps.get(pdbfile))
890    {
891  0 if (cm.getModelNumber() == modelId)
892    {
893  0 pdbfilename = pdbfile;
894  0 break findfileloop;
895    }
896    }
897    }
898  0 return pdbfilename;
899    }
900   
 
901  0 toggle private void log(String message)
902    {
903  0 System.err.println("## Chimera log: " + message);
904    }
905   
 
906  0 toggle private void viewerCommandHistory(boolean enable)
907    {
908    // log("(Not yet implemented) History "
909    // + ((debug || enable) ? "on" : "off"));
910    }
911   
 
912  0 toggle public long getLoadNotifiesHandled()
913    {
914  0 return loadNotifiesHandled;
915    }
916   
 
917  0 toggle @Override
918    public void setJalviewColourScheme(ColourSchemeI cs)
919    {
920  0 colourBySequence = false;
921   
922  0 if (cs == null)
923    {
924  0 return;
925    }
926   
927    // Chimera expects RBG values in the range 0-1
928  0 final double normalise = 255D;
929  0 viewerCommandHistory(false);
930  0 StringBuilder command = new StringBuilder(128);
931   
932  0 List<String> residueSet = ResidueProperties.getResidues(isNucleotide(),
933    false);
934  0 for (String resName : residueSet)
935    {
936  0 char res = resName.length() == 3
937    ? ResidueProperties.getSingleCharacterCode(resName)
938    : resName.charAt(0);
939  0 Color col = cs.findColour(res, 0, null, null, 0f);
940  0 command.append("color " + col.getRed() / normalise + ","
941    + col.getGreen() / normalise + "," + col.getBlue() / normalise
942    + " ::" + resName + ";");
943    }
944   
945  0 sendAsynchronousCommand(command.toString(), COLOURING_CHIMERA);
946  0 viewerCommandHistory(true);
947    }
948   
949    /**
950    * called when the binding thinks the UI needs to be refreshed after a Chimera
951    * state change. this could be because structures were loaded, or because an
952    * error has occurred.
953    */
954    public abstract void refreshGUI();
955   
 
956  0 toggle @Override
957    public void setLoadingFromArchive(boolean loadingFromArchive)
958    {
959  0 this.loadingFromArchive = loadingFromArchive;
960    }
961   
962    /**
963    *
964    * @return true if Chimeral is still restoring state or loading is still going
965    * on (see setFinsihedLoadingFromArchive)
966    */
 
967  0 toggle @Override
968    public boolean isLoadingFromArchive()
969    {
970  0 return loadingFromArchive && !loadingFinished;
971    }
972   
973    /**
974    * modify flag which controls if sequence colouring events are honoured by the
975    * binding. Should be true for normal operation
976    *
977    * @param finishedLoading
978    */
 
979  0 toggle @Override
980    public void setFinishedLoadingFromArchive(boolean finishedLoading)
981    {
982  0 loadingFinished = finishedLoading;
983    }
984   
985    /**
986    * Send the Chimera 'background solid <color>" command.
987    *
988    * @see https
989    * ://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/background
990    * .html
991    * @param col
992    */
 
993  0 toggle @Override
994    public void setBackgroundColour(Color col)
995    {
996  0 viewerCommandHistory(false);
997  0 double normalise = 255D;
998  0 final String command = "background solid " + col.getRed() / normalise
999    + "," + col.getGreen() / normalise + ","
1000    + col.getBlue() / normalise + ";";
1001  0 viewer.sendChimeraCommand(command, false);
1002  0 viewerCommandHistory(true);
1003    }
1004   
1005    /**
1006    * Ask Chimera to save its session to the given file. Returns true if
1007    * successful, else false.
1008    *
1009    * @param filepath
1010    * @return
1011    */
 
1012  0 toggle public boolean saveSession(String filepath)
1013    {
1014  0 if (isChimeraRunning())
1015    {
1016  0 List<String> reply = viewer.sendChimeraCommand("save " + filepath,
1017    true);
1018  0 if (reply.contains("Session written"))
1019    {
1020  0 return true;
1021    }
1022    else
1023    {
1024  0 Cache.log
1025    .error("Error saving Chimera session: " + reply.toString());
1026    }
1027    }
1028  0 return false;
1029    }
1030   
1031    /**
1032    * Ask Chimera to open a session file. Returns true if successful, else false.
1033    * The filename must have a .py extension for this command to work.
1034    *
1035    * @param filepath
1036    * @return
1037    */
 
1038  0 toggle public boolean openSession(String filepath)
1039    {
1040  0 sendChimeraCommand("open " + filepath, true);
1041    // todo: test for failure - how?
1042  0 return true;
1043    }
1044   
1045    /**
1046    * Returns a list of chains mapped in this viewer. Note this list is not
1047    * currently scoped per structure.
1048    *
1049    * @return
1050    */
 
1051  0 toggle @Override
1052    public List<String> getChainNames()
1053    {
1054  0 return chainNames;
1055    }
1056   
1057    /**
1058    * Send a 'focus' command to Chimera to recentre the visible display
1059    */
 
1060  0 toggle public void focusView()
1061    {
1062  0 sendChimeraCommand("focus", false);
1063    }
1064   
1065    /**
1066    * Send a 'show' command for all atoms in the currently selected columns
1067    *
1068    * TODO: pull up to abstract structure viewer interface
1069    *
1070    * @param vp
1071    */
 
1072  0 toggle public void highlightSelection(AlignmentViewPanel vp)
1073    {
1074  0 List<Integer> cols = vp.getAlignViewport().getColumnSelection()
1075    .getSelected();
1076  0 AlignmentI alignment = vp.getAlignment();
1077  0 StructureSelectionManager sm = getSsm();
1078  0 for (SequenceI seq : alignment.getSequences())
1079    {
1080    /*
1081    * convert selected columns into sequence positions
1082    */
1083  0 int[] positions = new int[cols.size()];
1084  0 int i = 0;
1085  0 for (Integer col : cols)
1086    {
1087  0 positions[i++] = seq.findPosition(col);
1088    }
1089  0 sm.highlightStructure(this, seq, positions);
1090    }
1091    }
1092   
1093    /**
1094    * Constructs and send commands to Chimera to set attributes on residues for
1095    * features visible in Jalview
1096    *
1097    * @param avp
1098    * @return
1099    */
 
1100  0 toggle public int sendFeaturesToViewer(AlignmentViewPanel avp)
1101    {
1102    // TODO refactor as required to pull up to an interface
1103  0 AlignmentI alignment = avp.getAlignment();
1104   
1105  0 String[] files = getStructureFiles();
1106  0 if (files == null)
1107    {
1108  0 return 0;
1109    }
1110   
1111  0 StructureMappingcommandSet commandSet = ChimeraCommands
1112    .getSetAttributeCommandsForFeatures(getSsm(), files,
1113    getSequence(), avp);
1114  0 String[] commands = commandSet.commands;
1115  0 if (commands.length > 10)
1116    {
1117  0 sendCommandsByFile(commands);
1118    }
1119    else
1120    {
1121  0 for (String command : commands)
1122    {
1123  0 sendAsynchronousCommand(command, null);
1124    }
1125    }
1126  0 return commands.length;
1127    }
1128   
1129    /**
1130    * Write commands to a temporary file, and send a command to Chimera to open
1131    * the file as a commands script. For use when sending a large number of
1132    * separate commands would overload the REST interface mechanism.
1133    *
1134    * @param commands
1135    */
 
1136  0 toggle protected void sendCommandsByFile(String[] commands)
1137    {
1138  0 try
1139    {
1140  0 File tmp = File.createTempFile("chim", ".com");
1141  0 tmp.deleteOnExit();
1142  0 PrintWriter out = new PrintWriter(new FileOutputStream(tmp));
1143  0 for (String command : commands)
1144    {
1145  0 out.println(command);
1146    }
1147  0 out.flush();
1148  0 out.close();
1149  0 String path = tmp.getAbsolutePath();
1150  0 sendAsynchronousCommand("open cmd:" + path, null);
1151    } catch (IOException e)
1152    {
1153  0 System.err.println("Sending commands to Chimera via file failed with "
1154    + e.getMessage());
1155    }
1156    }
1157   
1158    /**
1159    * Get Chimera residues which have the named attribute, find the mapped
1160    * positions in the Jalview sequence(s), and set as sequence features
1161    *
1162    * @param attName
1163    * @param alignmentPanel
1164    */
 
1165  0 toggle public void copyStructureAttributesToFeatures(String attName,
1166    AlignmentViewPanel alignmentPanel)
1167    {
1168    // todo pull up to AAStructureBindingModel (and interface?)
1169   
1170    /*
1171    * ask Chimera to list residues with the attribute, reporting its value
1172    */
1173    // this alternative command
1174    // list residues spec ':*/attName' attr attName
1175    // doesn't report 'None' values (which is good), but
1176    // fails for 'average.bfactor' (which is bad):
1177   
1178  0 String cmd = "list residues attr '" + attName + "'";
1179  0 List<String> residues = sendChimeraCommand(cmd, true);
1180   
1181  0 boolean featureAdded = createFeaturesForAttributes(attName, residues);
1182  0 if (featureAdded)
1183    {
1184  0 alignmentPanel.getFeatureRenderer().featuresAdded();
1185    }
1186    }
1187   
1188    /**
1189    * Create features in Jalview for the given attribute name and structure
1190    * residues.
1191    *
1192    * <pre>
1193    * The residue list should be 0, 1 or more reply lines of the format:
1194    * residue id #0:5.A isHelix -155.000836316 index 5
1195    * or
1196    * residue id #0:6.A isHelix None
1197    * </pre>
1198    *
1199    * @param attName
1200    * @param residues
1201    * @return
1202    */
 
1203  0 toggle protected boolean createFeaturesForAttributes(String attName,
1204    List<String> residues)
1205    {
1206  0 boolean featureAdded = false;
1207  0 String featureGroup = getViewerFeatureGroup();
1208   
1209  0 for (String residue : residues)
1210    {
1211  0 AtomSpec spec = null;
1212  0 String[] tokens = residue.split(" ");
1213  0 if (tokens.length < 5)
1214    {
1215  0 continue;
1216    }
1217  0 String atomSpec = tokens[2];
1218  0 String attValue = tokens[4];
1219   
1220    /*
1221    * ignore 'None' (e.g. for phi) or 'False' (e.g. for isHelix)
1222    */
1223  0 if ("None".equalsIgnoreCase(attValue)
1224    || "False".equalsIgnoreCase(attValue))
1225    {
1226  0 continue;
1227    }
1228   
1229  0 try
1230    {
1231  0 spec = AtomSpec.fromChimeraAtomspec(atomSpec);
1232    } catch (IllegalArgumentException e)
1233    {
1234  0 System.err.println("Problem parsing atomspec " + atomSpec);
1235  0 continue;
1236    }
1237   
1238  0 String chainId = spec.getChain();
1239  0 String description = attValue;
1240  0 float score = Float.NaN;
1241  0 try
1242    {
1243  0 score = Float.valueOf(attValue);
1244  0 description = chainId;
1245    } catch (NumberFormatException e)
1246    {
1247    // was not a float value
1248    }
1249   
1250  0 String pdbFile = getPdbFileForModel(spec.getModelNumber());
1251  0 spec.setPdbFile(pdbFile);
1252   
1253  0 List<AtomSpec> atoms = Collections.singletonList(spec);
1254   
1255    /*
1256    * locate the mapped position in the alignment (if any)
1257    */
1258  0 SearchResultsI sr = getSsm()
1259    .findAlignmentPositionsForStructurePositions(atoms);
1260   
1261    /*
1262    * expect one matched alignment position, or none
1263    * (if the structure position is not mapped)
1264    */
1265  0 for (SearchResultMatchI m : sr.getResults())
1266    {
1267  0 SequenceI seq = m.getSequence();
1268  0 int start = m.getStart();
1269  0 int end = m.getEnd();
1270  0 SequenceFeature sf = new SequenceFeature(attName, description,
1271    start, end, score, featureGroup);
1272    // todo: should SequenceFeature have an explicit property for chain?
1273    // note: repeating the action shouldn't duplicate features
1274  0 featureAdded |= seq.addSequenceFeature(sf);
1275    }
1276    }
1277  0 return featureAdded;
1278    }
1279   
1280    /**
1281    * Answers the feature group name to apply to features created in Jalview from
1282    * Chimera attributes
1283    *
1284    * @return
1285    */
 
1286  0 toggle protected String getViewerFeatureGroup()
1287    {
1288    // todo pull up to interface
1289  0 return CHIMERA_FEATURE_GROUP;
1290    }
1291   
 
1292  0 toggle public Hashtable<String, String> getChainFile()
1293    {
1294  0 return chainFile;
1295    }
1296   
 
1297  0 toggle public List<ChimeraModel> getChimeraModelByChain(String chain)
1298    {
1299  0 return chimeraMaps.get(chainFile.get(chain));
1300    }
1301   
 
1302  0 toggle public int getModelNoForChain(String chain)
1303    {
1304  0 List<ChimeraModel> foundModels = getChimeraModelByChain(chain);
1305  0 if (foundModels != null && !foundModels.isEmpty())
1306    {
1307  0 return foundModels.get(0).getModelNumber();
1308    }
1309  0 return -1;
1310    }
1311    }