Clover icon

Coverage Report

  1. Project Clover database Thu Aug 13 2020 12:04:21 BST
  2. Package jalview.structures.models

File AAStructureBindingModel.java

 

Coverage histogram

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

Code metrics

252
492
77
2
1,943
1,196
241
0.49
6.39
38.5
3.13

Classes

Class Line # Actions
AAStructureBindingModel 77 490 240
0.583129658.3%
AAStructureBindingModel.SuperposeData 84 2 1
1.0100%
 

Contributing tests

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