Clover icon

Coverage Report

  1. Project Clover database Mon Sep 2 2024 17:57:51 BST
  2. Package jalview.structures.models

File AAStructureBindingModel.java

 

Coverage histogram

../../../img/srcFileCovDistChart6.png
37% of files have more coverage

Code metrics

262
514
80
2
2,033
1,255
253
0.49
6.43
40
3.16

Classes

Class Line # Actions
AAStructureBindingModel 81 512 252
0.5990621459.9%
AAStructureBindingModel.SuperposeData 88 2 1
1.0100%
 

Contributing tests

This file is covered by 49 tests. .

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.structures.models;
22   
23    import java.awt.Color;
24    import java.io.File;
25    import java.io.IOException;
26    import java.util.ArrayList;
27    import java.util.Arrays;
28    import java.util.BitSet;
29    import java.util.Collections;
30    import java.util.HashMap;
31    import java.util.LinkedHashMap;
32    import java.util.List;
33    import java.util.Locale;
34    import java.util.Map;
35   
36    import javax.swing.SwingUtilities;
37   
38    import jalview.api.AlignViewportI;
39    import jalview.api.AlignmentViewPanel;
40    import jalview.api.FeatureRenderer;
41    import jalview.api.SequenceRenderer;
42    import jalview.api.StructureSelectionManagerProvider;
43    import jalview.api.structures.JalviewStructureDisplayI;
44    import jalview.bin.Console;
45    import jalview.datamodel.AlignmentI;
46    import jalview.datamodel.ColumnSelection;
47    import jalview.datamodel.HiddenColumns;
48    import jalview.datamodel.MappedFeatures;
49    import jalview.datamodel.PDBEntry;
50    import jalview.datamodel.SequenceFeature;
51    import jalview.datamodel.SequenceI;
52    import jalview.ext.rbvi.chimera.JalviewChimeraBinding;
53    import jalview.gui.AlignmentPanel;
54    import jalview.gui.Desktop;
55    import jalview.gui.StructureViewer.ViewerType;
56    import jalview.io.DataSourceType;
57    import jalview.io.StructureFile;
58    import jalview.renderer.seqfeatures.FeatureColourFinder;
59    import jalview.schemes.ColourSchemeI;
60    import jalview.schemes.ResidueProperties;
61    import jalview.structure.AtomSpec;
62    import jalview.structure.AtomSpecModel;
63    import jalview.structure.StructureCommandI;
64    import jalview.structure.StructureCommandsI;
65    import jalview.structure.StructureCommandsI.AtomSpecType;
66    import jalview.structure.StructureListener;
67    import jalview.structure.StructureMapping;
68    import jalview.structure.StructureSelectionManager;
69    import jalview.util.Comparison;
70    import jalview.util.MessageManager;
71   
72    /**
73    *
74    * A base class to hold common function for 3D structure model binding. Initial
75    * version created by refactoring JMol and Chimera binding models, but other
76    * structure viewers could in principle be accommodated in future.
77    *
78    * @author gmcarstairs
79    *
80    */
 
81    public abstract class AAStructureBindingModel
82    extends SequenceStructureBindingModel
83    implements StructureListener, StructureSelectionManagerProvider
84    {
85    /**
86    * Data bean class to simplify parameterisation in superposeStructures
87    */
 
88    public static class SuperposeData
89    {
90    public String filename;
91   
92    public String pdbId;
93   
94    public String chain = "";
95   
96    /**
97    * is the mapped sequence not protein ?
98    */
99    public boolean isRna;
100   
101    /*
102    * The pdb residue number (if any) mapped to columns of the alignment
103    */
104    public int[] pdbResNo; // or use SparseIntArray?
105   
106    public String modelId;
107   
108    /**
109    * Constructor
110    *
111    * @param width
112    * width of alignment (number of columns that may potentially
113    * participate in superposition)
114    * @param model
115    * structure viewer model number
116    */
 
117  9 toggle public SuperposeData(int width, String model)
118    {
119  9 pdbResNo = new int[width];
120  9 modelId = model;
121    }
122    }
123   
124    private static final int MIN_POS_TO_SUPERPOSE = 4;
125   
126    private static final String COLOURING_STRUCTURES = MessageManager
127    .getString("status.colouring_structures");
128   
129    /*
130    * the Jalview panel through which the user interacts
131    * with the structure viewer
132    */
133    private JalviewStructureDisplayI viewer;
134   
135    /*
136    * helper that generates command syntax
137    */
138    private StructureCommandsI commandGenerator;
139   
140    private StructureSelectionManager ssm;
141   
142    /*
143    * modelled chains, formatted as "pdbid:chainCode"
144    */
145    private List<String> chainNames;
146   
147    /*
148    * lookup of pdb file name by key "pdbid:chainCode"
149    */
150    private Map<String, String> chainFile;
151   
152    /*
153    * distinct PDB entries (pdb files) associated
154    * with sequences
155    */
156    private PDBEntry[] pdbEntry;
157   
158    /*
159    * sequences mapped to each pdbentry
160    */
161    private SequenceI[][] sequence;
162   
163    /*
164    * array of target chains for sequences - tied to pdbentry and sequence[]
165    */
166    private String[][] chains;
167   
168    /*
169    * datasource protocol for access to PDBEntrylatest
170    */
171    DataSourceType protocol = null;
172   
173    protected boolean colourBySequence = true;
174   
175    /**
176    * true if all sequences appear to be nucleotide
177    */
178    private boolean nucleotide;
179   
180    private boolean finishedInit = false;
181   
182    /**
183    * current set of model filenames loaded in the viewer
184    */
185    protected String[] modelFileNames = null;
186   
187    public String fileLoadingError;
188   
189    protected Thread externalViewerMonitor;
190   
191    /**
192    * Constructor
193    *
194    * @param ssm
195    * @param seqs
196    */
 
197  51 toggle public AAStructureBindingModel(StructureSelectionManager ssm,
198    SequenceI[][] seqs)
199    {
200  51 this.ssm = ssm;
201  51 this.sequence = seqs;
202  51 chainNames = new ArrayList<>();
203  51 chainFile = new HashMap<>();
204    }
205   
206    /**
207    * Constructor
208    *
209    * @param ssm
210    * @param pdbentry
211    * @param sequenceIs
212    * @param protocol
213    */
 
214  51 toggle public AAStructureBindingModel(StructureSelectionManager ssm,
215    PDBEntry[] pdbentry, SequenceI[][] sequenceIs,
216    DataSourceType protocol)
217    {
218  51 this(ssm, sequenceIs);
219  51 this.nucleotide = Comparison.isNucleotide(sequenceIs);
220  51 this.pdbEntry = pdbentry;
221  51 this.protocol = protocol;
222  51 resolveChains();
223    }
224   
 
225  51 toggle private boolean resolveChains()
226    {
227    /**
228    * final count of chain mappings discovered
229    */
230  51 int chainmaps = 0;
231    // JBPNote: JAL-2693 - this should be a list of chain mappings per
232    // [pdbentry][sequence]
233  51 String[][] newchains = new String[pdbEntry.length][];
234  51 int pe = 0;
235  51 for (PDBEntry pdb : pdbEntry)
236    {
237  60 SequenceI[] seqsForPdb = sequence[pe];
238  60 if (seqsForPdb != null)
239    {
240  60 newchains[pe] = new String[seqsForPdb.length];
241  60 int se = 0;
242  60 for (SequenceI asq : seqsForPdb)
243    {
244  72 String chain = (chains != null && chains[pe] != null)
245    ? chains[pe][se]
246    : null;
247  72 SequenceI sq = (asq.getDatasetSequence() == null) ? asq
248    : asq.getDatasetSequence();
249  72 if (sq.getAllPDBEntries() != null)
250    {
251  72 for (PDBEntry pdbentry : sq.getAllPDBEntries())
252    {
253  86 if (pdb.getFile() != null && pdbentry.getFile() != null
254    && pdb.getFile().equals(pdbentry.getFile()))
255    {
256  54 String chaincode = pdbentry.getChainCode();
257  54 if (chaincode != null && chaincode.length() > 0)
258    {
259  41 chain = chaincode;
260  41 chainmaps++;
261  41 break;
262    }
263    }
264    }
265    }
266  72 newchains[pe][se] = chain;
267  72 se++;
268    }
269  60 pe++;
270    }
271    }
272   
273  51 chains = newchains;
274  51 return chainmaps > 0;
275    }
276   
 
277  810 toggle public StructureSelectionManager getSsm()
278    {
279  810 return ssm;
280    }
281   
282    /**
283    * Returns the i'th PDBEntry (or null)
284    *
285    * @param i
286    * @return
287    */
 
288  280 toggle public PDBEntry getPdbEntry(int i)
289    {
290  280 return (pdbEntry != null && pdbEntry.length > i) ? pdbEntry[i] : null;
291    }
292   
293    /**
294    * Answers true if this binding includes the given PDB id, else false
295    *
296    * @param pdbId
297    * @return
298    */
 
299  1 toggle public boolean hasPdbId(String pdbId)
300    {
301  1 if (pdbEntry != null)
302    {
303  1 for (PDBEntry pdb : pdbEntry)
304    {
305  1 if (pdb.getId().equals(pdbId))
306    {
307  0 return true;
308    }
309    }
310    }
311  1 return false;
312    }
313   
314    /**
315    * Returns the number of modelled PDB file entries.
316    *
317    * @return
318    */
 
319  536 toggle public int getPdbCount()
320    {
321  536 return pdbEntry == null ? 0 : pdbEntry.length;
322    }
323   
 
324  1105 toggle public SequenceI[][] getSequence()
325    {
326  1105 return sequence;
327    }
328   
 
329  535 toggle public String[][] getChains()
330    {
331  535 return chains;
332    }
333   
 
334  0 toggle public DataSourceType getProtocol()
335    {
336  0 return protocol;
337    }
338   
339    // TODO may remove this if calling methods can be pulled up here
 
340  1 toggle protected void setPdbentry(PDBEntry[] pdbentry)
341    {
342  1 this.pdbEntry = pdbentry;
343    }
344   
 
345  1 toggle protected void setSequence(SequenceI[][] sequence)
346    {
347  1 this.sequence = sequence;
348    }
349   
 
350  1 toggle protected void setChains(String[][] chains)
351    {
352  1 this.chains = chains;
353    }
354   
355    /**
356    * Construct a title string for the viewer window based on the data Jalview
357    * knows about
358    *
359    * @param viewerName
360    * TODO
361    * @param verbose
362    *
363    * @return
364    */
 
365  94 toggle public String getViewerTitle(String viewerName, boolean verbose)
366    {
367  94 if (getSequence() == null || getSequence().length < 1
368    || getPdbCount() < 1 || getSequence()[0].length < 1)
369    {
370  0 return ("Jalview " + viewerName + " Window");
371    }
372    // TODO: give a more informative title when multiple structures are
373    // displayed.
374  94 StringBuilder title = new StringBuilder(64);
375  94 final PDBEntry pdbe = getPdbEntry(0);
376  94 title.append(viewerName + " view for " + getSequence()[0][0].getName()
377    + ":" + pdbe.getId());
378   
379  94 if (verbose)
380    {
381  94 String method = (String) pdbe.getProperty("method");
382  94 if (method != null)
383    {
384  0 title.append(" Method: ").append(method);
385    }
386  94 String chain = (String) pdbe.getProperty("chains");
387  94 if (chain != null)
388    {
389  0 title.append(" Chain:").append(chain);
390    }
391    }
392  94 return title.toString();
393    }
394   
395    /**
396    * Called by after closeViewer is called, to release any resources and
397    * references so they can be garbage collected. Override if needed.
398    */
 
399  0 toggle protected void releaseUIResources()
400    {
401    }
402   
 
403  0 toggle @Override
404    public void releaseReferences(Object svl)
405    {
406    }
407   
 
408  159 toggle public boolean isColourBySequence()
409    {
410  159 return colourBySequence;
411    }
412   
413    /**
414    * Called when the binding thinks the UI needs to be refreshed after a
415    * structure viewer state change. This could be because structures were
416    * loaded, or because an error has occurred. Default does nothing, override as
417    * required.
418    */
 
419  0 toggle public void refreshGUI()
420    {
421    }
422   
423    /**
424    * Instruct the Jalview binding to update the pdbentries vector if necessary
425    * prior to matching the jmol view's contents to the list of structure files
426    * Jalview knows about. By default does nothing, override as required.
427    */
 
428  47 toggle public void refreshPdbEntries()
429    {
430    }
431   
 
432  102 toggle public void setColourBySequence(boolean colourBySequence)
433    {
434  102 this.colourBySequence = colourBySequence;
435    }
436   
 
437  78 toggle protected void addSequenceAndChain(int pe, SequenceI[] seq,
438    String[] tchain)
439    {
440  78 if (pe < 0 || pe >= getPdbCount())
441    {
442  0 throw new Error(MessageManager.formatMessage(
443    "error.implementation_error_no_pdbentry_from_index",
444    new Object[]
445    { Integer.valueOf(pe).toString() }));
446    }
447  78 final String nullChain = "TheNullChain";
448  78 List<SequenceI> s = new ArrayList<>();
449  78 List<String> c = new ArrayList<>();
450  78 if (getChains() == null)
451    {
452  0 setChains(new String[getPdbCount()][]);
453    }
454  78 if (getSequence()[pe] != null)
455    {
456  189 for (int i = 0; i < getSequence()[pe].length; i++)
457    {
458  112 s.add(getSequence()[pe][i]);
459  112 if (getChains()[pe] != null)
460    {
461  110 if (i < getChains()[pe].length)
462    {
463  110 c.add(getChains()[pe][i]);
464    }
465    else
466    {
467  0 c.add(nullChain);
468    }
469    }
470    else
471    {
472  2 if (tchain != null && tchain.length > 0)
473    {
474  0 c.add(nullChain);
475    }
476    }
477    }
478    }
479  191 for (int i = 0; i < seq.length; i++)
480    {
481  113 if (!s.contains(seq[i]))
482    {
483  1 s.add(seq[i]);
484  1 if (tchain != null && i < tchain.length)
485    {
486  0 c.add(tchain[i] == null ? nullChain : tchain[i]);
487    }
488    }
489    }
490  78 SequenceI[] tmp = s.toArray(new SequenceI[s.size()]);
491  78 getSequence()[pe] = tmp;
492  78 if (c.size() > 0)
493    {
494  75 String[] tch = c.toArray(new String[c.size()]);
495  185 for (int i = 0; i < tch.length; i++)
496    {
497  110 if (tch[i] == nullChain)
498    {
499  0 tch[i] = null;
500    }
501    }
502  75 getChains()[pe] = tch;
503    }
504    else
505    {
506  3 getChains()[pe] = null;
507    }
508    }
509   
510    /**
511    * add structures and any known sequence associations
512    *
513    * @returns the pdb entries added to the current set.
514    */
 
515  1 toggle public synchronized PDBEntry[] addSequenceAndChain(PDBEntry[] pdbe,
516    SequenceI[][] seq, String[][] chns)
517    {
518  1 List<PDBEntry> v = new ArrayList<>();
519  1 List<int[]> rtn = new ArrayList<>();
520  2 for (int i = 0; i < getPdbCount(); i++)
521    {
522  1 v.add(getPdbEntry(i));
523    }
524  2 for (int i = 0; i < pdbe.length; i++)
525    {
526  1 int r = v.indexOf(pdbe[i]);
527  1 if (r == -1 || r >= getPdbCount())
528    {
529  1 rtn.add(new int[] { v.size(), i });
530  1 v.add(pdbe[i]);
531    }
532    else
533    {
534    // just make sure the sequence/chain entries are all up to date
535  0 addSequenceAndChain(r, seq[i], chns[i]);
536    }
537    }
538  1 pdbe = v.toArray(new PDBEntry[v.size()]);
539  1 setPdbentry(pdbe);
540  1 if (rtn.size() > 0)
541    {
542    // expand the tied sequence[] and string[] arrays
543  1 SequenceI[][] sqs = new SequenceI[getPdbCount()][];
544  1 String[][] sch = new String[getPdbCount()][];
545  1 System.arraycopy(getSequence(), 0, sqs, 0, getSequence().length);
546  1 System.arraycopy(getChains(), 0, sch, 0, this.getChains().length);
547  1 setSequence(sqs);
548  1 setChains(sch);
549  1 pdbe = new PDBEntry[rtn.size()];
550  2 for (int r = 0; r < pdbe.length; r++)
551    {
552  1 int[] stri = (rtn.get(r));
553    // record the pdb file as a new addition
554  1 pdbe[r] = getPdbEntry(stri[0]);
555    // and add the new sequence/chain entries
556  1 addSequenceAndChain(stri[0], seq[stri[1]], chns[stri[1]]);
557    }
558    }
559    else
560    {
561  0 pdbe = null;
562    }
563  1 return pdbe;
564    }
565   
566    /**
567    * Add sequences to the pe'th pdbentry's sequence set.
568    *
569    * @param pe
570    * @param seq
571    */
 
572  77 toggle public void addSequence(int pe, SequenceI[] seq)
573    {
574  77 addSequenceAndChain(pe, seq, null);
575    }
576   
577    /**
578    * add the given sequences to the mapping scope for the given pdb file handle
579    *
580    * @param pdbFile
581    * - pdbFile identifier
582    * @param seq
583    * - set of sequences it can be mapped to
584    */
 
585  33 toggle public void addSequenceForStructFile(String pdbFile, SequenceI[] seq)
586    {
587  66 for (int pe = 0; pe < getPdbCount(); pe++)
588    {
589  33 if (getPdbEntry(pe).getFile().equals(pdbFile))
590    {
591  28 addSequence(pe, seq);
592    }
593    }
594    }
595   
596    @Override
597    public abstract void highlightAtoms(List<AtomSpec> atoms);
598   
 
599  0 toggle protected boolean isNucleotide()
600    {
601  0 return this.nucleotide;
602    }
603   
604    /**
605    * Returns a readable description of all mappings for the wrapped pdbfile to
606    * any mapped sequences
607    *
608    * @param pdbfile
609    * @param seqs
610    * @return
611    */
 
612  0 toggle public String printMappings()
613    {
614  0 if (pdbEntry == null)
615    {
616  0 return "";
617    }
618  0 StringBuilder sb = new StringBuilder(128);
619  0 for (int pdbe = 0; pdbe < getPdbCount(); pdbe++)
620    {
621  0 String pdbfile = getPdbEntry(pdbe).getFile();
622  0 List<SequenceI> seqs = Arrays.asList(getSequence()[pdbe]);
623  0 sb.append(getSsm().printMappings(pdbfile, seqs));
624    }
625  0 return sb.toString();
626    }
627   
628    /**
629    * Returns the mapped structure position for a given aligned column of a given
630    * sequence, or -1 if the column is gapped, beyond the end of the sequence, or
631    * not mapped to structure.
632    *
633    * @param seq
634    * @param alignedPos
635    * @param mapping
636    * @return
637    */
 
638  620 toggle protected int getMappedPosition(SequenceI seq, int alignedPos,
639    StructureMapping mapping)
640    {
641  620 if (alignedPos >= seq.getLength())
642    {
643  1 return -1;
644    }
645   
646  619 if (Comparison.isGap(seq.getCharAt(alignedPos)))
647    {
648  6 return -1;
649    }
650  613 int seqPos = seq.findPosition(alignedPos);
651  613 int pos = mapping.getPDBResNum(seqPos);
652  613 return pos;
653    }
654   
655    /**
656    * Helper method to identify residues that can participate in a structure
657    * superposition command. For each structure, identify a sequence in the
658    * alignment which is mapped to the structure. Identify non-gapped columns in
659    * the sequence which have a mapping to a residue in the structure. Returns
660    * the index of the first structure that has a mapping to the alignment.
661    *
662    * @param alignment
663    * the sequence alignment which is the basis of structure
664    * superposition
665    * @param matched
666    * a BitSet, where bit j is set to indicate that every structure has
667    * a mapped residue present in column j (so the column can
668    * participate in structure alignment)
669    * @param structures
670    * an array of data beans corresponding to pdb file index
671    * @return
672    */
 
673  3 toggle protected int findSuperposableResidues(AlignmentI alignment,
674    BitSet matched,
675    AAStructureBindingModel.SuperposeData[] structures)
676    {
677  3 int refStructure = -1;
678  3 String[] files = getStructureFiles();
679  3 if (files == null)
680    {
681  0 return -1;
682    }
683  11 for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
684    {
685  8 StructureMapping[] mappings = getSsm().getMapping(files[pdbfnum]);
686  8 int lastPos = -1;
687   
688    /*
689    * Find the first mapped sequence (if any) for this PDB entry which is in
690    * the alignment
691    */
692  8 final int seqCountForPdbFile = getSequence()[pdbfnum].length;
693  16 for (int s = 0; s < seqCountForPdbFile; s++)
694    {
695  8 for (StructureMapping mapping : mappings)
696    {
697  8 final SequenceI theSequence = getSequence()[pdbfnum][s];
698  8 if (mapping.getSequence() == theSequence
699    && alignment.findIndex(theSequence) > -1)
700    {
701  8 if (refStructure < 0)
702    {
703  3 refStructure = pdbfnum;
704    }
705  638 for (int r = 0; r < alignment.getWidth(); r++)
706    {
707  630 if (!matched.get(r))
708    {
709  10 continue;
710    }
711  620 int pos = getMappedPosition(theSequence, r, mapping);
712  620 if (pos < 1 || pos == lastPos)
713    {
714  7 matched.clear(r);
715  7 continue;
716    }
717  613 lastPos = pos;
718  613 structures[pdbfnum].pdbResNo[r] = pos;
719    }
720  8 String chain = mapping.getChain();
721  8 if (chain != null && chain.trim().length() > 0)
722    {
723  8 structures[pdbfnum].chain = chain;
724    }
725  8 structures[pdbfnum].pdbId = mapping.getPdbId();
726  8 structures[pdbfnum].isRna = !theSequence.isProtein();
727   
728    /*
729    * move on to next pdb file (ignore sequences for other chains
730    * for the same structure)
731    */
732  8 s = seqCountForPdbFile;
733  8 break; // fixme break out of two loops here!
734    }
735    }
736    }
737    }
738  3 return refStructure;
739    }
740   
741    /**
742    * Returns true if the structure viewer has loaded all of the files of
743    * interest (identified by the file mapping having been set up), or false if
744    * any are still not loaded after a timeout interval.
745    *
746    * @param files
747    */
 
748  1 toggle protected boolean waitForFileLoad(String[] files)
749    {
750    /*
751    * give up after 10 secs plus 1 sec per file
752    */
753  1 long starttime = System.currentTimeMillis();
754  1 long endTime = 10000 + 1000 * files.length + starttime;
755  1 String notLoaded = null;
756   
757  1 boolean waiting = true;
758  2 while (waiting && System.currentTimeMillis() < endTime)
759    {
760  1 waiting = false;
761  1 for (String file : files)
762    {
763  2 notLoaded = file;
764  2 if (file == null)
765    {
766  0 continue;
767    }
768  2 try
769    {
770  2 StructureMapping[] sm = getSsm().getMapping(file);
771  2 if (sm == null || sm.length == 0)
772    {
773  0 waiting = true;
774    }
775    } catch (Throwable x)
776    {
777  0 waiting = true;
778    }
779    }
780    }
781   
782  1 if (waiting)
783    {
784  0 jalview.bin.Console.errPrintln(
785    "Timed out waiting for structure viewer to load file "
786    + notLoaded);
787  0 return false;
788    }
789  1 return true;
790    }
791   
 
792  0 toggle @Override
793    public boolean isListeningFor(SequenceI seq)
794    {
795  0 if (sequence != null)
796    {
797  0 for (SequenceI[] seqs : sequence)
798    {
799  0 if (seqs != null)
800    {
801  0 for (SequenceI s : seqs)
802    {
803  0 if (s == seq || (s.getDatasetSequence() != null
804    && s.getDatasetSequence() == seq.getDatasetSequence()))
805    {
806  0 return true;
807    }
808    }
809    }
810    }
811    }
812  0 return false;
813    }
814   
 
815  429 toggle public boolean isFinishedInit()
816    {
817  429 return finishedInit;
818    }
819   
 
820  90 toggle public void setFinishedInit(boolean fi)
821    {
822  90 this.finishedInit = fi;
823    }
824   
825    /**
826    * Returns a list of chains mapped in this viewer, formatted as
827    * "pdbid:chainCode"
828    *
829    * @return
830    */
 
831  112 toggle public List<String> getChainNames()
832    {
833  112 return chainNames;
834    }
835   
836    /**
837    * Returns the Jalview panel hosting the structure viewer (if any)
838    *
839    * @return
840    */
 
841  2801 toggle public JalviewStructureDisplayI getViewer()
842    {
843  2801 return viewer;
844    }
845   
 
846  68 toggle public void setViewer(JalviewStructureDisplayI v)
847    {
848  68 viewer = v;
849    }
850   
851    /**
852    * Constructs and sends a command to align structures against a reference
853    * structure, based on one or more sequence alignments. May optionally return
854    * an error or warning message for the alignment command(s).
855    *
856    * @param alignWith
857    * an array of one or more alignment views to process
858    * @return
859    */
 
860  1 toggle public String superposeStructures(List<AlignmentViewPanel> alignWith)
861    {
862  1 String error = "";
863  1 String[] files = getStructureFiles();
864   
865  1 if (!waitForFileLoad(files))
866    {
867  0 return null;
868    }
869  1 refreshPdbEntries();
870   
871  1 for (AlignmentViewPanel view : alignWith)
872    {
873  1 AlignmentI alignment = view.getAlignment();
874  1 HiddenColumns hiddenCols = alignment.getHiddenColumns();
875    /*
876    * 'matched' bit i will be set for visible alignment columns i where
877    * all sequences have a residue with a mapping to their PDB structure
878    */
879  1 final int width = alignment.getWidth();
880  1 BitSet matched = new BitSet();
881  1 ColumnSelection cs = view.getAlignViewport().getColumnSelection();
882    // restrict to active column selection, if there is one
883  1 if (cs != null && cs.hasSelectedColumns()
884    && cs.getSelected().size() >= 4)
885    {
886  0 for (int s : cs.getSelected())
887    {
888  0 matched.set(s);
889    }
890    }
891    else
892    {
893  298 for (int m = 0; m < width; m++)
894    {
895  297 if (hiddenCols == null || hiddenCols.isVisible(m))
896    {
897  297 matched.set(m);
898    }
899    }
900    }
901  1 AAStructureBindingModel.SuperposeData[] structures = new AAStructureBindingModel.SuperposeData[files.length];
902  3 for (int f = 0; f < files.length; f++)
903    {
904  2 structures[f] = new AAStructureBindingModel.SuperposeData(width,
905    getModelIdForFile(files[f]));
906    }
907   
908    /*
909    * Calculate the superposable alignment columns ('matched'), and the
910    * corresponding structure residue positions (structures.pdbResNo)
911    */
912  1 int refStructure = findSuperposableResidues(alignment, matched,
913    structures);
914   
915    /*
916    * require at least 4 positions to be able to execute superposition
917    */
918  1 int nmatched = matched.cardinality();
919  1 if (nmatched < MIN_POS_TO_SUPERPOSE)
920    {
921  0 String msg = MessageManager
922    .formatMessage("label.insufficient_residues", nmatched);
923  0 error += view.getViewName() + ": " + msg + "; ";
924  0 continue;
925    }
926   
927    /*
928    * get a model of the superposable residues in the reference structure
929    */
930  1 AtomSpecModel refAtoms = getAtomSpec(structures[refStructure],
931    matched);
932   
933    /*
934    * Show all as backbone before doing superposition(s)
935    * (residues used for matching will be shown as ribbon)
936    */
937    // todo better way to ensure synchronous than setting getReply true!!
938  1 executeCommands(commandGenerator.showBackbone(), true, null);
939   
940  1 AtomSpecType backbone = structures[refStructure].isRna
941    ? AtomSpecType.PHOSPHATE
942    : AtomSpecType.ALPHA;
943  1 List<AtomSpecModel> models = new ArrayList<AtomSpecModel>();
944  1 models.add(refAtoms);
945    /*
946    * superpose each (other) structure to the reference in turn
947    */
948  3 for (int i = 0; i < structures.length; i++)
949    {
950  2 if (i != refStructure)
951    {
952  1 AtomSpecModel atomSpec = getAtomSpec(structures[i], matched);
953  1 List<StructureCommandI> commands = commandGenerator
954    .superposeStructures(refAtoms, atomSpec, backbone);
955  1 List<String> replies = executeCommands(commands, true, null);
956  1 for (String reply : replies)
957    {
958    // return this error (Chimera only) to the user
959  0 if (reply.toLowerCase(Locale.ROOT)
960    .contains("unequal numbers of atoms"))
961    {
962  0 error += "; " + reply;
963    }
964    }
965  1 models.add(atomSpec);
966    }
967    }
968  1 List<StructureCommandI> finalView = commandGenerator
969    .centerViewOn(models);
970  1 if (finalView != null && finalView.size() > 0)
971    {
972  1 executeCommands(finalView, false, "Centered on Superposition");
973    }
974    }
975  1 return error;
976    }
977   
 
978  2 toggle private AtomSpecModel getAtomSpec(
979    AAStructureBindingModel.SuperposeData superposeData,
980    BitSet matched)
981    {
982  2 AtomSpecModel model = new AtomSpecModel();
983  2 int nextColumnMatch = matched.nextSetBit(0);
984  594 while (nextColumnMatch != -1)
985    {
986  592 int pdbResNum = superposeData.pdbResNo[nextColumnMatch];
987  592 model.addRange(superposeData.modelId, pdbResNum, pdbResNum,
988    superposeData.chain);
989  592 nextColumnMatch = matched.nextSetBit(nextColumnMatch + 1);
990    }
991   
992  2 return model;
993    }
994   
995    /**
996    * returns the current sequenceRenderer that should be used to colour the
997    * structures
998    *
999    * @param alignment
1000    *
1001    * @return
1002    */
1003    public abstract SequenceRenderer getSequenceRenderer(
1004    AlignmentViewPanel alignment);
1005   
1006    /**
1007    * Sends a command to the structure viewer to colour each chain with a
1008    * distinct colour (to the extent supported by the viewer)
1009    */
 
1010  0 toggle public void colourByChain()
1011    {
1012  0 colourBySequence = false;
1013   
1014    // TODO: JAL-628 colour chains distinctly across all visible models
1015   
1016  0 executeCommand(false, COLOURING_STRUCTURES,
1017    commandGenerator.colourByChain());
1018    }
1019   
1020    /**
1021    * Sends a command to the structure viewer to colour each chain with a
1022    * distinct colour (to the extent supported by the viewer)
1023    */
 
1024  0 toggle public void colourByCharge()
1025    {
1026  0 colourBySequence = false;
1027   
1028  0 executeCommands(commandGenerator.colourByCharge(), false,
1029    COLOURING_STRUCTURES);
1030    }
1031   
1032    /**
1033    * Sends a command to the structure to apply a colour scheme (defined in
1034    * Jalview but not necessarily applied to the alignment), which defines a
1035    * colour per residue letter. More complex schemes (e.g. that depend on
1036    * consensus) cannot be used here and are ignored.
1037    *
1038    * @param cs
1039    */
 
1040  0 toggle public void colourByJalviewColourScheme(ColourSchemeI cs)
1041    {
1042  0 colourBySequence = false;
1043   
1044  0 if (cs == null || !cs.isSimple())
1045    {
1046  0 return;
1047    }
1048   
1049    /*
1050    * build a map of {Residue3LetterCode, Color}
1051    */
1052  0 Map<String, Color> colours = new HashMap<>();
1053  0 List<String> residues = ResidueProperties.getResidues(isNucleotide(),
1054    false);
1055  0 for (String resName : residues)
1056    {
1057  0 char res = resName.length() == 3
1058    ? ResidueProperties.getSingleCharacterCode(resName)
1059    : resName.charAt(0);
1060  0 Color colour = cs.findColour(res, 0, null, null, 0f);
1061  0 colours.put(resName, colour);
1062    }
1063   
1064    /*
1065    * pass to the command constructor, and send the command
1066    */
1067  0 List<StructureCommandI> cmd = commandGenerator
1068    .colourByResidues(colours);
1069  0 executeCommands(cmd, false, COLOURING_STRUCTURES);
1070    }
1071   
 
1072  0 toggle public void setBackgroundColour(Color col)
1073    {
1074  0 StructureCommandI cmd = commandGenerator.setBackgroundColour(col);
1075  0 executeCommand(false, null, cmd);
1076    }
1077   
1078    /**
1079    * Execute one structure viewer command. If {@code getReply} is true, may
1080    * optionally return one or more reply messages, else returns null.
1081    *
1082    * @param cmd
1083    * @param getReply
1084    */
1085    protected abstract List<String> executeCommand(StructureCommandI cmd,
1086    boolean getReply);
1087   
1088    /**
1089    * Executes one or more structure viewer commands
1090    *
1091    * @param commands
1092    * @param getReply
1093    * @param msg
1094    */
 
1095  318 toggle public List<String> executeCommands(List<StructureCommandI> commands,
1096    boolean getReply, String msg)
1097    {
1098  318 return executeCommand(getReply, msg,
1099    commands.toArray(new StructureCommandI[commands.size()]));
1100    }
1101   
1102    /**
1103    * Executes one or more structure viewer commands, optionally returning the
1104    * reply, and optionally showing a status message while the command is being
1105    * executed.
1106    * <p>
1107    * If a reply is wanted, the execution is done synchronously (waits),
1108    * otherwise it is done in a separate thread (doesn't wait). WARNING: if you
1109    * are sending commands that need to execute before later calls to
1110    * executeCommand (e.g. mouseovers, which clean up after previous ones) then
1111    * set getReply true to ensure that commands are not executed out of order.
1112    *
1113    * @param getReply
1114    * @param msg
1115    * @param cmds
1116    * @return
1117    */
 
1118  318 toggle protected List<String> executeCommand(boolean getReply, String msg,
1119    StructureCommandI... cmds)
1120    {
1121  318 JalviewStructureDisplayI theViewer = getViewer();
1122  318 final long handle = msg == null ? 0 : theViewer.startProgressBar(msg);
1123   
1124  318 if (getReply)
1125    {
1126    /*
1127    * execute and wait for reply
1128    */
1129  2 List<String> response = new ArrayList<>();
1130  2 try
1131    {
1132  2 for (StructureCommandI cmd : cmds)
1133    {
1134  2 List<String> replies = executeCommand(cmd, true);
1135  2 if (replies != null)
1136    {
1137  0 response.addAll(replies);
1138    }
1139    }
1140  2 return response;
1141    } finally
1142    {
1143  2 if (msg != null)
1144    {
1145  0 theViewer.stopProgressBar(null, handle);
1146    }
1147    }
1148    }
1149   
1150    /*
1151    * fire and forget
1152    */
1153  316 String threadName = msg == null ? "StructureCommand" : msg;
1154  316 new Thread(new Runnable()
1155    {
 
1156  316 toggle @Override
1157    public void run()
1158    {
1159  316 try
1160    {
1161  316 for (StructureCommandI cmd : cmds)
1162    {
1163  306 executeCommand(cmd, false);
1164    }
1165    } finally
1166    {
1167  316 if (msg != null)
1168    {
1169  316 SwingUtilities.invokeLater(new Runnable()
1170    {
 
1171  315 toggle @Override
1172    public void run()
1173    {
1174  315 theViewer.stopProgressBar(null, handle);
1175    }
1176    });
1177    }
1178    }
1179    }
1180    }, threadName).start();
1181  316 return null;
1182    }
1183   
1184    /**
1185    * Colours any structures associated with sequences in the given alignment as
1186    * coloured in the alignment view, provided colourBySequence is enabled
1187    */
 
1188  329 toggle public void colourBySequence(AlignmentViewPanel alignmentv)
1189    {
1190  329 if (!colourBySequence || !isLoadingFinished() || getSsm() == null)
1191    {
1192  16 return;
1193    }
1194  313 Map<Object, AtomSpecModel> colourMap = buildColoursMap(ssm, sequence,
1195    alignmentv);
1196   
1197  305 List<StructureCommandI> colourBySequenceCommands = commandGenerator
1198    .colourBySequence(colourMap);
1199  305 executeCommands(colourBySequenceCommands, false, COLOURING_STRUCTURES);
1200    }
1201   
1202    /**
1203    * Centre the display in the structure viewer
1204    */
 
1205  0 toggle public void focusView()
1206    {
1207  0 executeCommand(false, null, commandGenerator.focusView());
1208    }
1209   
1210    /**
1211    * Generates and executes a command to show only specified chains in the
1212    * structure viewer. The list of chains to show should contain entries
1213    * formatted as "pdbid:chaincode".
1214    *
1215    * @param toShow
1216    */
 
1217  0 toggle public void showChains(List<String> toShow)
1218    {
1219    // todo or reformat toShow list entries as modelNo:pdbId:chainCode ?
1220   
1221    /*
1222    * Reformat the pdbid:chainCode values as modelNo:chainCode
1223    * since this is what is needed to construct the viewer command
1224    * todo: find a less messy way to do this
1225    */
1226  0 List<String> showThese = new ArrayList<>();
1227  0 for (String chainId : toShow)
1228    {
1229  0 String[] tokens = chainId.split("\\:");
1230  0 if (tokens.length == 2)
1231    {
1232  0 String pdbFile = getFileForChain(chainId);
1233  0 String model = getModelIdForFile(pdbFile);
1234  0 showThese.add(model + ":" + tokens[1]);
1235    }
1236    }
1237  0 executeCommands(commandGenerator.showChains(showThese), false, null);
1238    }
1239   
1240    /**
1241    * Answers the structure viewer's model id given a PDB file name. Returns an
1242    * empty string if model id is not found.
1243    *
1244    * @param chainId
1245    * @return
1246    */
1247    protected abstract String getModelIdForFile(String chainId);
1248   
 
1249  377 toggle public boolean hasFileLoadingError()
1250    {
1251  377 return fileLoadingError != null && fileLoadingError.length() > 0;
1252    }
1253   
1254    /**
1255    * Returns the FeatureRenderer for the given alignment view
1256    *
1257    * @param avp
1258    * @return
1259    */
 
1260  43 toggle public FeatureRenderer getFeatureRenderer(AlignmentViewPanel avp)
1261    {
1262  43 AlignmentViewPanel ap = (avp == null) ? getViewer().getAlignmentPanel()
1263    : avp;
1264  42 if (ap == null)
1265    {
1266  0 return null;
1267    }
1268  42 return ap.getFeatureRenderer();
1269    }
1270   
 
1271  45 toggle protected void setStructureCommands(StructureCommandsI cmd)
1272    {
1273  45 commandGenerator = cmd;
1274    }
1275   
1276    /**
1277    * Records association of one chain id (formatted as "pdbid:chainCode") with
1278    * the corresponding PDB file name
1279    *
1280    * @param chainId
1281    * @param fileName
1282    */
 
1283  63 toggle public void addChainFile(String chainId, String fileName)
1284    {
1285  63 chainFile.put(chainId, fileName);
1286    }
1287   
1288    /**
1289    * Returns the PDB filename for the given chain id (formatted as
1290    * "pdbid:chainCode"), or null if not found
1291    *
1292    * @param chainId
1293    * @return
1294    */
 
1295  0 toggle protected String getFileForChain(String chainId)
1296    {
1297  0 return chainFile.get(chainId);
1298    }
1299   
 
1300  1177 toggle @Override
1301    public void updateColours(Object source)
1302    {
1303  1177 if (getViewer() == null)
1304    {
1305    // can happen if a viewer was not instantiated or cleaned up and is still
1306    // registered - mostly during tests
1307  44 return;
1308    }
1309  1133 AlignmentViewPanel ap = (AlignmentViewPanel) source;
1310    // ignore events from panels not used to colour this view
1311  1133 if (!getViewer().isUsedForColourBy(ap))
1312    {
1313  846 return;
1314    }
1315  287 if (!isLoadingFromArchive())
1316    {
1317  287 colourBySequence(ap);
1318    }
1319    }
1320   
 
1321  24 toggle public StructureCommandsI getCommandGenerator()
1322    {
1323  24 return commandGenerator;
1324    }
1325   
1326    protected abstract ViewerType getViewerType();
1327   
1328    /**
1329    * Builds a data structure which records mapped structure residues for each
1330    * colour. From this we can easily generate the viewer commands for colour by
1331    * sequence. Constructs and returns a map of {@code Color} to
1332    * {@code AtomSpecModel}, where the atomspec model holds
1333    *
1334    * <pre>
1335    * Model ids
1336    * Chains
1337    * Residue positions
1338    * </pre>
1339    *
1340    * Ordering is by order of addition (for colours), natural ordering (for
1341    * models and chains)
1342    *
1343    * @param ssm
1344    * @param sequence
1345    * @param viewPanel
1346    * @return
1347    */
 
1348  314 toggle protected Map<Object, AtomSpecModel> buildColoursMap(
1349    StructureSelectionManager ssm, SequenceI[][] sequence,
1350    AlignmentViewPanel viewPanel)
1351    {
1352  314 String[] files = getStructureFiles();
1353  314 SequenceRenderer sr = getSequenceRenderer(viewPanel);
1354  314 FeatureRenderer fr = viewPanel.getFeatureRenderer();
1355  314 FeatureColourFinder finder = new FeatureColourFinder(fr);
1356  314 AlignViewportI viewport = viewPanel.getAlignViewport();
1357  314 HiddenColumns cs = viewport.getAlignment().getHiddenColumns();
1358  306 AlignmentI al = viewport.getAlignment();
1359  306 Map<Object, AtomSpecModel> colourMap = new LinkedHashMap<>();
1360  306 Color lastColour = null;
1361   
1362  615 for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
1363    {
1364  309 final String modelId = getModelIdForFile(files[pdbfnum]);
1365  309 StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
1366   
1367  309 if (mapping == null || mapping.length < 1)
1368    {
1369  31 continue;
1370    }
1371   
1372  278 int startPos = -1, lastPos = -1;
1373  278 String lastChain = "";
1374  556 for (int s = 0; s < sequence[pdbfnum].length; s++)
1375    {
1376  612 for (int sp, m = 0; m < mapping.length; m++)
1377    {
1378  334 final SequenceI seq = sequence[pdbfnum][s];
1379  ? if (mapping[m].getSequence() == seq
1380    && (sp = al.findIndex(seq)) > -1)
1381    {
1382  273 SequenceI asp = al.getSequenceAt(sp);
1383  71407 for (int r = 0; r < asp.getLength(); r++)
1384    {
1385    // no mapping to gaps in sequence
1386  71129 if (Comparison.isGap(asp.getCharAt(r)))
1387    {
1388  33614 continue;
1389    }
1390  37524 int pos = mapping[m].getPDBResNum(asp.findPosition(r));
1391   
1392  37525 if (pos < 1 || pos == lastPos)
1393    {
1394  16 continue;
1395    }
1396   
1397  37511 Color colour = sr.getResidueColour(seq, r, finder);
1398   
1399    /*
1400    * darker colour for hidden regions
1401    */
1402  37511 if (!cs.isVisible(r))
1403    {
1404  6 colour = Color.GRAY;
1405    }
1406   
1407  37511 final String chain = mapping[m].getChain();
1408   
1409    /*
1410    * Just keep incrementing the end position for this colour range
1411    * _unless_ colour, PDB model or chain has changed, or there is a
1412    * gap in the mapped residue sequence
1413    */
1414  37508 final boolean newColour = !colour.equals(lastColour);
1415  37508 final boolean nonContig = lastPos + 1 != pos;
1416  37503 final boolean newChain = !chain.equals(lastChain);
1417  37507 if (newColour || nonContig || newChain)
1418    {
1419  7503 if (startPos != -1)
1420    {
1421  7230 addAtomSpecRange(colourMap, lastColour, modelId, startPos,
1422    lastPos, lastChain);
1423    }
1424  7502 startPos = pos;
1425    }
1426  37505 lastColour = colour;
1427  37507 lastPos = pos;
1428  37509 lastChain = chain;
1429    }
1430    // final colour range
1431  273 if (lastColour != null)
1432    {
1433  273 addAtomSpecRange(colourMap, lastColour, modelId, startPos,
1434    lastPos, lastChain);
1435    }
1436    // break;
1437    }
1438    }
1439    }
1440    }
1441  306 return colourMap;
1442    }
1443   
1444    /**
1445    * todo better refactoring (map lookup or similar to get viewer structure id)
1446    *
1447    * @param pdbfnum
1448    * @param file
1449    * @return
1450    */
 
1451  0 toggle protected String getModelId(int pdbfnum, String file)
1452    {
1453  0 return String.valueOf(pdbfnum);
1454    }
1455   
1456    /**
1457    * Saves chains, formatted as "pdbId:chainCode", and lookups from this to the
1458    * full PDB file path
1459    *
1460    * @param pdb
1461    * @param file
1462    */
 
1463  44 toggle public void stashFoundChains(StructureFile pdb, String file)
1464    {
1465  107 for (int i = 0; i < pdb.getChains().size(); i++)
1466    {
1467  63 String chid = pdb.getId() + ":" + pdb.getChains().elementAt(i).id;
1468  63 addChainFile(chid, file);
1469  63 getChainNames().add(chid);
1470    }
1471    }
1472   
1473    /**
1474    * Helper method to add one contiguous range to the AtomSpec model for the
1475    * given value (creating the model if necessary). As used by Jalview,
1476    * {@code value} is
1477    * <ul>
1478    * <li>a colour, when building a 'colour structure by sequence' command</li>
1479    * <li>a feature value, when building a 'set Chimera attributes from features'
1480    * command</li>
1481    * </ul>
1482    *
1483    * @param map
1484    * @param value
1485    * @param model
1486    * @param startPos
1487    * @param endPos
1488    * @param chain
1489    */
 
1490  7502 toggle public static final void addAtomSpecRange(Map<Object, AtomSpecModel> map,
1491    Object value, String model, int startPos, int endPos,
1492    String chain)
1493    {
1494    /*
1495    * Get/initialize map of data for the colour
1496    */
1497  7502 AtomSpecModel atomSpec = map.get(value);
1498  7502 if (atomSpec == null)
1499    {
1500  1612 atomSpec = new AtomSpecModel();
1501  1612 map.put(value, atomSpec);
1502    }
1503   
1504  7501 atomSpec.addRange(model, startPos, endPos, chain);
1505    }
1506   
1507    /**
1508    * Returns the file extension (including '.' separator) to use for a saved
1509    * viewer session file. Default is to return null (not supported), override as
1510    * required.
1511    *
1512    * @return
1513    */
 
1514  0 toggle public String getSessionFileExtension()
1515    {
1516  0 return null;
1517    }
1518   
1519    /**
1520    * If supported, saves the state of the structure viewer to a temporary file
1521    * and returns the file. Returns null and logs an error on any failure.
1522    *
1523    * @return
1524    */
 
1525  2 toggle public File saveSession()
1526    {
1527  2 String prefix = getViewerType().toString();
1528  2 String suffix = getSessionFileExtension();
1529  2 File f = null;
1530  2 try
1531    {
1532  2 f = File.createTempFile(prefix, suffix);
1533  2 saveSession(f);
1534    } catch (IOException e)
1535    {
1536  0 Console.error(String.format("Error saving %s session: %s", prefix,
1537    e.toString()));
1538    }
1539   
1540  2 return f;
1541    }
1542   
1543    /**
1544    * Use restoreSession when you want to restore a previously saved sesssion to
1545    * the running viewer instance.
1546    *
1547    * @param absolutePath
1548    */
 
1549  0 toggle public void restoreSession(String absolutePath)
1550    {
1551  0 String prefix = getViewerType().toString();
1552  0 try
1553    {
1554   
1555  0 StructureCommandI cmd = commandGenerator.restoreSession(absolutePath);
1556  0 if (cmd != null)
1557    {
1558  0 executeCommand(cmd, false);
1559    }
1560    } catch (Throwable e)
1561    {
1562  0 Console.error(String.format("Error restoring %s session: %s", prefix,
1563    e.toString()));
1564    }
1565   
1566    }
1567   
1568    /**
1569    * Saves the structure viewer session to the given file
1570    *
1571    * @param f
1572    */
 
1573  2 toggle protected void saveSession(File f)
1574    {
1575  2 StructureCommandI cmd = commandGenerator.saveSession(f.getPath());
1576  2 if (cmd != null)
1577    {
1578  2 executeCommand(cmd, false);
1579    }
1580    }
1581   
1582    /**
1583    * Returns true if the viewer is an external structure viewer for which the
1584    * process is still alive, else false (for Jmol, or an external viewer which
1585    * the user has independently closed)
1586    *
1587    * @return
1588    */
 
1589  23 toggle public boolean isViewerRunning()
1590    {
1591  23 return false;
1592    }
1593   
1594    /**
1595    * Closes Jalview's structure viewer panel and releases associated resources.
1596    * If it is managing an external viewer program, and {@code forceClose} is
1597    * true, also asks that program to close.
1598    *
1599    * @param forceClose
1600    */
 
1601  23 toggle public void closeViewer(boolean forceClose)
1602    {
1603  23 getSsm().removeStructureViewerListener(this, this.getStructureFiles());
1604  23 releaseUIResources();
1605   
1606    /*
1607    * end the thread that closes this panel if the external viewer closes
1608    */
1609  23 if (externalViewerMonitor != null)
1610    {
1611  0 externalViewerMonitor.interrupt();
1612  0 externalViewerMonitor = null;
1613    }
1614   
1615  23 stopListening();
1616   
1617  23 if (forceClose)
1618    {
1619  1 StructureCommandI cmd = getCommandGenerator().closeViewer();
1620  1 if (cmd != null)
1621    {
1622  0 executeCommand(cmd, false);
1623    }
1624    }
1625    }
1626   
1627    /**
1628    * Returns the URL of a help page for the structure viewer, or null if none is
1629    * known
1630    *
1631    * @return
1632    */
 
1633  0 toggle public String getHelpURL()
1634    {
1635  0 return null;
1636    }
1637   
1638    /**
1639    * <pre>
1640    * Helper method to build a map of
1641    * { featureType, { feature value, AtomSpecModel } }
1642    * </pre>
1643    *
1644    * @param viewPanel
1645    * @return
1646    */
 
1647  0 toggle protected Map<String, Map<Object, AtomSpecModel>> buildFeaturesMap(
1648    AlignmentViewPanel viewPanel)
1649    {
1650  0 Map<String, Map<Object, AtomSpecModel>> theMap = new LinkedHashMap<>();
1651  0 String[] files = getStructureFiles();
1652  0 if (files == null)
1653    {
1654  0 return theMap;
1655    }
1656   
1657  0 FeatureRenderer fr = viewPanel.getFeatureRenderer();
1658  0 if (fr == null)
1659    {
1660  0 return theMap;
1661    }
1662   
1663  0 AlignViewportI viewport = viewPanel.getAlignViewport();
1664  0 List<String> visibleFeatures = fr.getDisplayedFeatureTypes();
1665   
1666    /*
1667    * if alignment is showing features from complement, we also transfer
1668    * these features to the corresponding mapped structure residues
1669    */
1670  0 boolean showLinkedFeatures = viewport.isShowComplementFeatures();
1671  0 List<String> complementFeatures = new ArrayList<>();
1672  0 FeatureRenderer complementRenderer = null;
1673  0 if (showLinkedFeatures)
1674    {
1675  0 AlignViewportI comp = fr.getViewport().getCodingComplement();
1676  0 if (comp != null)
1677    {
1678  0 complementRenderer = Desktop.getAlignFrameFor(comp)
1679    .getFeatureRenderer();
1680  0 complementFeatures = complementRenderer.getDisplayedFeatureTypes();
1681    }
1682    }
1683  0 if (visibleFeatures.isEmpty() && complementFeatures.isEmpty())
1684    {
1685  0 return theMap;
1686    }
1687   
1688  0 AlignmentI alignment = viewPanel.getAlignment();
1689  0 SequenceI[][] seqs = getSequence();
1690   
1691  0 for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
1692    {
1693  0 String modelId = getModelIdForFile(files[pdbfnum]);
1694  0 StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
1695   
1696  0 if (mapping == null || mapping.length < 1)
1697    {
1698  0 continue;
1699    }
1700   
1701  0 for (int seqNo = 0; seqNo < seqs[pdbfnum].length; seqNo++)
1702    {
1703  0 for (int m = 0; m < mapping.length; m++)
1704    {
1705  0 final SequenceI seq = seqs[pdbfnum][seqNo];
1706  0 int sp = alignment.findIndex(seq);
1707  0 StructureMapping structureMapping = mapping[m];
1708  0 if (structureMapping.getSequence() == seq && sp > -1)
1709    {
1710    /*
1711    * found a sequence with a mapping to a structure;
1712    * now scan its features
1713    */
1714  0 if (!visibleFeatures.isEmpty())
1715    {
1716  0 scanSequenceFeatures(visibleFeatures, structureMapping, seq,
1717    theMap, modelId);
1718    }
1719  0 if (showLinkedFeatures)
1720    {
1721  0 scanComplementFeatures(complementRenderer, structureMapping,
1722    seq, theMap, modelId);
1723    }
1724    }
1725    }
1726    }
1727    }
1728  0 return theMap;
1729    }
1730   
1731    /**
1732    * Ask the structure viewer to open a session file. Returns true if
1733    * successful, else false (or not supported).
1734    *
1735    * @param filepath
1736    * @return
1737    */
 
1738  0 toggle public boolean openSession(String filepath)
1739    {
1740  0 StructureCommandI cmd = getCommandGenerator().openSession(filepath);
1741  0 if (cmd == null)
1742    {
1743  0 return false;
1744    }
1745  0 executeCommand(cmd, true);
1746    // todo: test for failure - how?
1747  0 return true;
1748    }
1749   
1750    /**
1751    * Scans visible features in mapped positions of the CDS/peptide complement,
1752    * and adds any found to the map of attribute values/structure positions
1753    *
1754    * @param complementRenderer
1755    * @param structureMapping
1756    * @param seq
1757    * @param theMap
1758    * @param modelNumber
1759    */
 
1760  0 toggle protected static void scanComplementFeatures(
1761    FeatureRenderer complementRenderer,
1762    StructureMapping structureMapping, SequenceI seq,
1763    Map<String, Map<Object, AtomSpecModel>> theMap,
1764    String modelNumber)
1765    {
1766    /*
1767    * for each sequence residue mapped to a structure position...
1768    */
1769  0 for (int seqPos : structureMapping.getMapping().keySet())
1770    {
1771    /*
1772    * find visible complementary features at mapped position(s)
1773    */
1774  0 MappedFeatures mf = complementRenderer
1775    .findComplementFeaturesAtResidue(seq, seqPos);
1776  0 if (mf != null)
1777    {
1778  0 for (SequenceFeature sf : mf.features)
1779    {
1780  0 String type = sf.getType();
1781   
1782    /*
1783    * Don't copy features which originated from Chimera
1784    */
1785  0 if (JalviewChimeraBinding.CHIMERA_FEATURE_GROUP
1786    .equals(sf.getFeatureGroup()))
1787    {
1788  0 continue;
1789    }
1790   
1791    /*
1792    * record feature 'value' (score/description/type) as at the
1793    * corresponding structure position
1794    */
1795  0 List<int[]> mappedRanges = structureMapping
1796    .getPDBResNumRanges(seqPos, seqPos);
1797   
1798  0 if (!mappedRanges.isEmpty())
1799    {
1800  0 String value = sf.getDescription();
1801  0 if (value == null || value.length() == 0)
1802    {
1803  0 value = type;
1804    }
1805  0 float score = sf.getScore();
1806  0 if (score != 0f && !Float.isNaN(score))
1807    {
1808  0 value = Float.toString(score);
1809    }
1810  0 Map<Object, AtomSpecModel> featureValues = theMap.get(type);
1811  0 if (featureValues == null)
1812    {
1813  0 featureValues = new HashMap<>();
1814  0 theMap.put(type, featureValues);
1815    }
1816  0 for (int[] range : mappedRanges)
1817    {
1818  0 addAtomSpecRange(featureValues, value, modelNumber, range[0],
1819    range[1], structureMapping.getChain());
1820    }
1821    }
1822    }
1823    }
1824    }
1825    }
1826   
1827    /**
1828    * Inspect features on the sequence; for each feature that is visible,
1829    * determine its mapped ranges in the structure (if any) according to the
1830    * given mapping, and add them to the map.
1831    *
1832    * @param visibleFeatures
1833    * @param mapping
1834    * @param seq
1835    * @param theMap
1836    * @param modelId
1837    */
 
1838  0 toggle protected static void scanSequenceFeatures(List<String> visibleFeatures,
1839    StructureMapping mapping, SequenceI seq,
1840    Map<String, Map<Object, AtomSpecModel>> theMap, String modelId)
1841    {
1842  0 List<SequenceFeature> sfs = seq.getFeatures().getPositionalFeatures(
1843    visibleFeatures.toArray(new String[visibleFeatures.size()]));
1844  0 for (SequenceFeature sf : sfs)
1845    {
1846  0 String type = sf.getType();
1847   
1848    /*
1849    * Don't copy features which originated from Chimera
1850    */
1851  0 if (JalviewChimeraBinding.CHIMERA_FEATURE_GROUP
1852    .equals(sf.getFeatureGroup()))
1853    {
1854  0 continue;
1855    }
1856   
1857  0 List<int[]> mappedRanges = mapping.getPDBResNumRanges(sf.getBegin(),
1858    sf.getEnd());
1859   
1860  0 if (!mappedRanges.isEmpty())
1861    {
1862  0 String value = sf.getDescription();
1863  0 if (value == null || value.length() == 0)
1864    {
1865  0 value = type;
1866    }
1867  0 float score = sf.getScore();
1868  0 if (score != 0f && !Float.isNaN(score))
1869    {
1870  0 value = Float.toString(score);
1871    }
1872  0 Map<Object, AtomSpecModel> featureValues = theMap.get(type);
1873  0 if (featureValues == null)
1874    {
1875  0 featureValues = new HashMap<>();
1876  0 theMap.put(type, featureValues);
1877    }
1878  0 for (int[] range : mappedRanges)
1879    {
1880  0 addAtomSpecRange(featureValues, value, modelId, range[0],
1881    range[1], mapping.getChain());
1882    }
1883    }
1884    }
1885    }
1886   
1887    /**
1888    * Returns the number of structure files in the structure viewer and mapped to
1889    * Jalview. This may be zero if the files are still in the process of loading
1890    * in the viewer.
1891    *
1892    * @return
1893    */
 
1894  150 toggle public int getMappedStructureCount()
1895    {
1896  150 String[] files = getStructureFiles();
1897  150 return files == null ? 0 : files.length;
1898    }
1899   
1900    /**
1901    * Starts a thread that waits for the external viewer program process to
1902    * finish, so that we can then close the associated resources. This avoids
1903    * leaving orphaned viewer panels in Jalview if the user closes the external
1904    * viewer.
1905    *
1906    * @param p
1907    */
 
1908  0 toggle protected void startExternalViewerMonitor(Process p)
1909    {
1910  0 externalViewerMonitor = new Thread(new Runnable()
1911    {
1912   
 
1913  0 toggle @Override
1914    public void run()
1915    {
1916  0 try
1917    {
1918  0 p.waitFor();
1919  0 JalviewStructureDisplayI display = getViewer();
1920  0 if (display != null)
1921    {
1922  0 display.closeViewer(false);
1923    }
1924    } catch (InterruptedException e)
1925    {
1926    // exit thread if Chimera Viewer is closed in Jalview
1927    }
1928    }
1929    });
1930  0 externalViewerMonitor.start();
1931    }
1932   
1933    /**
1934    * If supported by the external structure viewer, sends it commands to notify
1935    * model or selection changes to the specified URL (where Jalview has started
1936    * a listener)
1937    *
1938    * @param uri
1939    */
 
1940  0 toggle protected void startListening(String uri)
1941    {
1942  0 List<StructureCommandI> commands = getCommandGenerator()
1943    .startNotifications(uri);
1944  0 if (commands != null)
1945    {
1946  0 executeCommands(commands, false, null);
1947    }
1948    }
1949   
1950    /**
1951    * If supported by the external structure viewer, sends it commands to stop
1952    * notifying model or selection changes
1953    */
 
1954  23 toggle protected void stopListening()
1955    {
1956  23 List<StructureCommandI> commands = getCommandGenerator()
1957    .stopNotifications();
1958  23 if (commands != null)
1959    {
1960  0 executeCommands(commands, false, null);
1961    }
1962    }
1963   
1964    /**
1965    * If supported by the structure viewer, queries it for all residue attributes
1966    * with the given attribute name, and creates features on corresponding
1967    * residues of the alignment. Returns the number of features added.
1968    *
1969    * @param attName
1970    * @param alignmentPanel
1971    * @return
1972    */
 
1973  0 toggle public int copyStructureAttributesToFeatures(String attName,
1974    AlignmentPanel alignmentPanel)
1975    {
1976  0 StructureCommandI cmd = getCommandGenerator()
1977    .getResidueAttributes(attName);
1978  0 if (cmd == null)
1979    {
1980  0 return 0;
1981    }
1982  0 List<String> residueAttributes = executeCommand(cmd, true);
1983   
1984  0 int featuresAdded = createFeaturesForAttributes(attName,
1985    residueAttributes);
1986  0 if (featuresAdded > 0)
1987    {
1988  0 alignmentPanel.getFeatureRenderer().featuresAdded();
1989    }
1990  0 return featuresAdded;
1991    }
1992   
1993    /**
1994    * Parses {@code residueAttributes} and creates sequence features on any
1995    * mapped alignment residues. Returns the number of features created.
1996    * <p>
1997    * {@code residueAttributes} is the reply from the structure viewer to a
1998    * command to list any residue attributes for the given attribute name. Syntax
1999    * and parsing of this is viewer-specific.
2000    *
2001    * @param attName
2002    * @param residueAttributes
2003    * @return
2004    */
 
2005  0 toggle protected int createFeaturesForAttributes(String attName,
2006    List<String> residueAttributes)
2007    {
2008  0 return 0;
2009    }
2010   
2011    /**
2012    * list the ligands available for display/hiding in the current view
2013    *
2014    * @return HETATM CODE:Molecule name
2015    */
 
2016  0 toggle public Map<String, String> getHetatmNames()
2017    {
2018  0 return Collections.EMPTY_MAP;
2019    }
2020   
2021    /**
2022    * Generates and executes a command to show the given hetatm types as CPK
2023    *
2024    * @param toShow
2025    * - one or more of strings from getHetatmNames
2026    */
 
2027  0 toggle public void showHetatms(List<String> toShow)
2028    {
2029  0 executeCommands(commandGenerator.showHetatms(toShow), false,
2030    "Adjusting hetatm visibility");
2031    }
2032   
2033    }