Clover icon

Coverage Report

  1. Project Clover database Mon Nov 18 2024 09:38:20 GMT
  2. Package jalview.structures.models

File AAStructureBindingModel.java

 

Coverage histogram

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

Code metrics

262
520
80
2
2,038
1,260
256
0.49
6.5
40
3.2

Classes

Class Line # Actions
AAStructureBindingModel 81 518 255
0.5634458756.3%
AAStructureBindingModel.SuperposeData 88 2 1
1.0100%
 

Contributing tests

This file is covered by 10 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  11 toggle public AAStructureBindingModel(StructureSelectionManager ssm,
198    SequenceI[][] seqs)
199    {
200  11 this.ssm = ssm;
201  11 this.sequence = seqs;
202  11 chainNames = new ArrayList<>();
203  11 chainFile = new HashMap<>();
204    }
205   
206    /**
207    * Constructor
208    *
209    * @param ssm
210    * @param pdbentry
211    * @param sequenceIs
212    * @param protocol
213    */
 
214  11 toggle public AAStructureBindingModel(StructureSelectionManager ssm,
215    PDBEntry[] pdbentry, SequenceI[][] sequenceIs,
216    DataSourceType protocol)
217    {
218  11 this(ssm, sequenceIs);
219  11 this.nucleotide = Comparison.isNucleotide(sequenceIs);
220  11 this.pdbEntry = pdbentry;
221  11 this.protocol = protocol;
222  11 resolveChains();
223    }
224   
 
225  11 toggle private boolean resolveChains()
226    {
227    /**
228    * final count of chain mappings discovered
229    */
230  11 int chainmaps = 0;
231    // JBPNote: JAL-2693 - this should be a list of chain mappings per
232    // [pdbentry][sequence]
233  11 String[][] newchains = new String[pdbEntry.length][];
234  11 int pe = 0;
235  11 for (PDBEntry pdb : pdbEntry)
236    {
237  20 SequenceI[] seqsForPdb = sequence[pe];
238  20 if (seqsForPdb != null)
239    {
240  20 newchains[pe] = new String[seqsForPdb.length];
241  20 int se = 0;
242  20 for (SequenceI asq : seqsForPdb)
243    {
244  25 String chain = (chains != null && chains[pe] != null)
245    ? chains[pe][se]
246    : null;
247  25 SequenceI sq = (asq.getDatasetSequence() == null) ? asq
248    : asq.getDatasetSequence();
249  25 if (sq.getAllPDBEntries() != null)
250    {
251  25 for (PDBEntry pdbentry : sq.getAllPDBEntries())
252    {
253  26 if (pdb.getFile() != null && pdbentry.getFile() != null
254    && pdb.getFile().equals(pdbentry.getFile()))
255    {
256  7 String chaincode = pdbentry.getChainCode();
257  7 if (chaincode != null && chaincode.length() > 0)
258    {
259  4 chain = chaincode;
260  4 chainmaps++;
261  4 break;
262    }
263    }
264    }
265    }
266  25 newchains[pe][se] = chain;
267  25 se++;
268    }
269  20 pe++;
270    }
271    }
272   
273  11 chains = newchains;
274  11 return chainmaps > 0;
275    }
276   
 
277  36 toggle public StructureSelectionManager getSsm()
278    {
279  36 return ssm;
280    }
281   
282    /**
283    * Returns the i'th PDBEntry (or null)
284    *
285    * @param i
286    * @return
287    */
 
288  45 toggle public PDBEntry getPdbEntry(int i)
289    {
290  45 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  68 toggle public int getPdbCount()
320    {
321  68 return pdbEntry == null ? 0 : pdbEntry.length;
322    }
323   
 
324  99 toggle public SequenceI[][] getSequence()
325    {
326  99 return sequence;
327    }
328   
 
329  40 toggle public String[][] getChains()
330    {
331  40 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  8 toggle public String getViewerTitle(String viewerName, boolean verbose)
366    {
367  8 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  8 StringBuilder title = new StringBuilder(64);
375  8 final PDBEntry pdbe = getPdbEntry(0);
376  8 title.append(viewerName + " view for " + getSequence()[0][0].getName()
377    + ":" + pdbe.getId());
378   
379  8 if (verbose)
380    {
381  8 String method = (String) pdbe.getProperty("method");
382  8 if (method != null)
383    {
384  0 title.append(" Method: ").append(method);
385    }
386  8 String chain = (String) pdbe.getProperty("chains");
387  8 if (chain != null)
388    {
389  0 title.append(" Chain:").append(chain);
390    }
391    }
392  8 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  13 toggle public boolean isColourBySequence()
409    {
410  13 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  5 toggle public void refreshPdbEntries()
429    {
430    }
431   
 
432  8 toggle public void setColourBySequence(boolean colourBySequence)
433    {
434  8 this.colourBySequence = colourBySequence;
435    }
436   
 
437  8 toggle protected void addSequenceAndChain(int pe, SequenceI[] seq,
438    String[] tchain)
439    {
440  8 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  8 final String nullChain = "TheNullChain";
448  8 List<SequenceI> s = new ArrayList<>();
449  8 List<String> c = new ArrayList<>();
450  8 if (getChains() == null)
451    {
452  0 setChains(new String[getPdbCount()][]);
453    }
454  8 if (getSequence()[pe] != null)
455    {
456  14 for (int i = 0; i < getSequence()[pe].length; i++)
457    {
458  7 s.add(getSequence()[pe][i]);
459  7 if (getChains()[pe] != null)
460    {
461  5 if (i < getChains()[pe].length)
462    {
463  5 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  16 for (int i = 0; i < seq.length; i++)
480    {
481  8 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  8 SequenceI[] tmp = s.toArray(new SequenceI[s.size()]);
491  8 getSequence()[pe] = tmp;
492  8 if (c.size() > 0)
493    {
494  5 String[] tch = c.toArray(new String[c.size()]);
495  10 for (int i = 0; i < tch.length; i++)
496    {
497  5 if (tch[i] == nullChain)
498    {
499  0 tch[i] = null;
500    }
501    }
502  5 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  7 toggle public void addSequence(int pe, SequenceI[] seq)
573    {
574  7 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  0 toggle public void addSequenceForStructFile(String pdbFile, SequenceI[] seq)
586    {
587  0 for (int pe = 0; pe < getPdbCount(); pe++)
588    {
589  0 if (getPdbEntry(pe).getFile().equals(pdbFile))
590    {
591  0 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  68 toggle public boolean isFinishedInit()
816    {
817  68 return finishedInit;
818    }
819   
 
820  10 toggle public void setFinishedInit(boolean fi)
821    {
822  10 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  16 toggle public List<String> getChainNames()
832    {
833  16 return chainNames;
834    }
835   
836    /**
837    * Returns the Jalview panel hosting the structure viewer (if any)
838    *
839    * @return
840    */
 
841  90 toggle public JalviewStructureDisplayI getViewer()
842    {
843  90 return viewer;
844    }
845   
 
846  7 toggle public void setViewer(JalviewStructureDisplayI v)
847    {
848  7 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  14 toggle public List<String> executeCommands(List<StructureCommandI> commands,
1096    boolean getReply, String msg)
1097    {
1098  14 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  14 toggle protected List<String> executeCommand(boolean getReply, String msg,
1119    StructureCommandI... cmds)
1120    {
1121  14 JalviewStructureDisplayI theViewer = getViewer();
1122  14 final long handle = msg == null ? 0 : theViewer.startProgressBar(msg);
1123   
1124  14 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  12 String threadName = msg == null ? "StructureCommand" : msg;
1154  12 new Thread(new Runnable()
1155    {
 
1156  12 toggle @Override
1157    public void run()
1158    {
1159  12 try
1160    {
1161  12 for (StructureCommandI cmd : cmds)
1162    {
1163  12 executeCommand(cmd, false);
1164    }
1165    } finally
1166    {
1167  12 if (msg != null)
1168    {
1169  12 SwingUtilities.invokeLater(new Runnable()
1170    {
 
1171  11 toggle @Override
1172    public void run()
1173    {
1174  11 theViewer.stopProgressBar(null, handle);
1175    }
1176    });
1177    }
1178    }
1179    }
1180    }, threadName).start();
1181  12 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  13 toggle public void colourBySequence(AlignmentViewPanel alignmentv)
1189    {
1190  13 if (!colourBySequence || !isLoadingFinished() || getSsm() == null)
1191    {
1192  0 return;
1193    }
1194  13 Map<Object, AtomSpecModel> colourMap = buildColoursMap(ssm, sequence,
1195    alignmentv);
1196   
1197  11 List<StructureCommandI> colourBySequenceCommands = commandGenerator
1198    .colourBySequence(colourMap);
1199  11 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  54 toggle public boolean hasFileLoadingError()
1250    {
1251  54 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  3 toggle public FeatureRenderer getFeatureRenderer(AlignmentViewPanel avp)
1261    {
1262  3 AlignmentViewPanel ap = (avp == null) ? getViewer().getAlignmentPanel()
1263    : avp;
1264  2 if (ap == null)
1265    {
1266  0 return null;
1267    }
1268  2 return ap.getFeatureRenderer();
1269    }
1270   
 
1271  5 toggle protected void setStructureCommands(StructureCommandsI cmd)
1272    {
1273  5 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  13 toggle public void addChainFile(String chainId, String fileName)
1284    {
1285  13 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  52 toggle @Override
1301    public void updateColours(Object source)
1302    {
1303  52 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  39 return;
1308    }
1309  13 AlignmentViewPanel ap = (AlignmentViewPanel) source;
1310    // ignore events from panels not used to colour this view
1311  13 if (!getViewer().isUsedForColourBy(ap))
1312    {
1313  3 return;
1314    }
1315  10 if (!isLoadingFromArchive())
1316    {
1317  10 colourBySequence(ap);
1318    }
1319    }
1320   
 
1321  3 toggle public StructureCommandsI getCommandGenerator()
1322    {
1323  3 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  14 toggle protected Map<Object, AtomSpecModel> buildColoursMap(
1349    StructureSelectionManager ssm, SequenceI[][] sequence,
1350    AlignmentViewPanel viewPanel)
1351    {
1352  14 String[] files = getStructureFiles();
1353  14 SequenceRenderer sr = getSequenceRenderer(viewPanel);
1354  14 FeatureRenderer fr = viewPanel.getFeatureRenderer();
1355  14 FeatureColourFinder finder = new FeatureColourFinder(fr);
1356  14 AlignViewportI viewport = viewPanel.getAlignViewport();
1357  14 HiddenColumns cs = viewport.getAlignment().getHiddenColumns();
1358  12 AlignmentI al = viewport.getAlignment();
1359  12 Map<Object, AtomSpecModel> colourMap = new LinkedHashMap<>();
1360  12 Color lastColour = null;
1361   
1362  27 for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
1363    {
1364  15 final String modelId = getModelIdForFile(files[pdbfnum]);
1365  15 StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
1366   
1367  15 if (mapping == null || mapping.length < 1)
1368    {
1369  2 continue;
1370    }
1371   
1372  13 int startPos = -1, lastPos = -1;
1373  13 String lastChain = "";
1374  26 for (int s = 0; s < sequence[pdbfnum].length; s++)
1375    {
1376  26 for (int sp, m = 0; m < mapping.length; m++)
1377    {
1378  13 final SequenceI seq = sequence[pdbfnum][s];
1379  ? if (mapping[m].getSequence() == seq
1380    && (sp = al.findIndex(seq)) > -1)
1381    {
1382  12 SequenceI asp = al.getSequenceAt(sp);
1383  2994 for (int r = 0; r < asp.getLength(); r++)
1384    {
1385    // no mapping to gaps in sequence
1386  2982 if (Comparison.isGap(asp.getCharAt(r)))
1387    {
1388  0 continue;
1389    }
1390  2982 int pos = mapping[m].getPDBResNum(asp.findPosition(r));
1391   
1392  2982 if (pos < 1 || pos == lastPos)
1393    {
1394  0 continue;
1395    }
1396   
1397  2982 Color colour = sr.getResidueColour(seq, r, finder);
1398   
1399    /*
1400    * darker colour for hidden regions
1401    */
1402  2982 if (!cs.isVisible(r))
1403    {
1404  6 colour = Color.GRAY;
1405    }
1406   
1407  2982 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  2982 final boolean newColour = !colour.equals(lastColour);
1415  2982 final boolean nonContig = lastPos + 1 != pos;
1416  2982 final boolean newChain = !chain.equals(lastChain);
1417  2982 if (newColour || nonContig || newChain)
1418    {
1419  18 if (startPos != -1)
1420    {
1421  6 addAtomSpecRange(colourMap, lastColour, modelId, startPos,
1422    lastPos, lastChain);
1423    }
1424  18 startPos = pos;
1425    }
1426  2982 lastColour = colour;
1427  2982 lastPos = pos;
1428  2982 lastChain = chain;
1429    }
1430    // final colour range
1431  12 if (lastColour != null && startPos!=-1)
1432    {
1433  12 addAtomSpecRange(colourMap, lastColour, modelId, startPos,
1434    lastPos, lastChain);
1435    }
1436    // break;
1437    }
1438    }
1439    }
1440    }
1441  12 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  4 toggle public void stashFoundChains(StructureFile pdb, String file)
1464    {
1465  17 for (int i = 0; i < pdb.getChains().size(); i++)
1466    {
1467  13 String chid = pdb.getId() + ":" + pdb.getChains().elementAt(i).id;
1468  13 addChainFile(chid, file);
1469  13 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  18 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  18 AtomSpecModel atomSpec = map.get(value);
1498  18 if (atomSpec == null)
1499    {
1500  13 atomSpec = new AtomSpecModel();
1501  13 map.put(value, atomSpec);
1502    }
1503   
1504  18 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  0 toggle public File saveSession()
1526    {
1527  0 String prefix = getViewerType().toString();
1528  0 String suffix = getSessionFileExtension();
1529  0 File f = null;
1530  0 try
1531    {
1532  0 f = File.createTempFile(prefix, suffix);
1533  0 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  0 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  0 toggle protected void saveSession(File f)
1574    {
1575  0 try {
1576  0 Console.trace("Saving session to "+f.getCanonicalPath().toString());
1577  0 } catch (Exception foo) {};
1578  0 StructureCommandI cmd = commandGenerator.saveSession(f.getPath());
1579  0 if (cmd != null)
1580    {
1581  0 executeCommand(cmd, true);
1582    }
1583  0 try {
1584  0 Console.trace("Done saving session to "+f.getCanonicalPath().toString());
1585  0 } catch (Exception foo) {}; }
1586   
1587    /**
1588    * Returns true if the viewer is an external structure viewer for which the
1589    * process is still alive, else false (for Jmol, or an external viewer which
1590    * the user has independently closed)
1591    *
1592    * @return
1593    */
 
1594  2 toggle public boolean isViewerRunning()
1595    {
1596  2 return false;
1597    }
1598   
1599    /**
1600    * Closes Jalview's structure viewer panel and releases associated resources.
1601    * If it is managing an external viewer program, and {@code forceClose} is
1602    * true, also asks that program to close.
1603    *
1604    * @param forceClose
1605    */
 
1606  2 toggle public void closeViewer(boolean forceClose)
1607    {
1608  2 getSsm().removeStructureViewerListener(this, this.getStructureFiles());
1609  2 releaseUIResources();
1610   
1611    /*
1612    * end the thread that closes this panel if the external viewer closes
1613    */
1614  2 if (externalViewerMonitor != null)
1615    {
1616  0 externalViewerMonitor.interrupt();
1617  0 externalViewerMonitor = null;
1618    }
1619   
1620  2 stopListening();
1621   
1622  2 if (forceClose)
1623    {
1624  1 StructureCommandI cmd = getCommandGenerator().closeViewer();
1625  1 if (cmd != null)
1626    {
1627  0 executeCommand(cmd, false);
1628    }
1629    }
1630    }
1631   
1632    /**
1633    * Returns the URL of a help page for the structure viewer, or null if none is
1634    * known
1635    *
1636    * @return
1637    */
 
1638  0 toggle public String getHelpURL()
1639    {
1640  0 return null;
1641    }
1642   
1643    /**
1644    * <pre>
1645    * Helper method to build a map of
1646    * { featureType, { feature value, AtomSpecModel } }
1647    * </pre>
1648    *
1649    * @param viewPanel
1650    * @return
1651    */
 
1652  0 toggle protected Map<String, Map<Object, AtomSpecModel>> buildFeaturesMap(
1653    AlignmentViewPanel viewPanel)
1654    {
1655  0 Map<String, Map<Object, AtomSpecModel>> theMap = new LinkedHashMap<>();
1656  0 String[] files = getStructureFiles();
1657  0 if (files == null)
1658    {
1659  0 return theMap;
1660    }
1661   
1662  0 FeatureRenderer fr = viewPanel.getFeatureRenderer();
1663  0 if (fr == null)
1664    {
1665  0 return theMap;
1666    }
1667   
1668  0 AlignViewportI viewport = viewPanel.getAlignViewport();
1669  0 List<String> visibleFeatures = fr.getDisplayedFeatureTypes();
1670   
1671    /*
1672    * if alignment is showing features from complement, we also transfer
1673    * these features to the corresponding mapped structure residues
1674    */
1675  0 boolean showLinkedFeatures = viewport.isShowComplementFeatures();
1676  0 List<String> complementFeatures = new ArrayList<>();
1677  0 FeatureRenderer complementRenderer = null;
1678  0 if (showLinkedFeatures)
1679    {
1680  0 AlignViewportI comp = fr.getViewport().getCodingComplement();
1681  0 if (comp != null)
1682    {
1683  0 complementRenderer = Desktop.getAlignFrameFor(comp)
1684    .getFeatureRenderer();
1685  0 complementFeatures = complementRenderer.getDisplayedFeatureTypes();
1686    }
1687    }
1688  0 if (visibleFeatures.isEmpty() && complementFeatures.isEmpty())
1689    {
1690  0 return theMap;
1691    }
1692   
1693  0 AlignmentI alignment = viewPanel.getAlignment();
1694  0 SequenceI[][] seqs = getSequence();
1695   
1696  0 for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
1697    {
1698  0 String modelId = getModelIdForFile(files[pdbfnum]);
1699  0 StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
1700   
1701  0 if (mapping == null || mapping.length < 1)
1702    {
1703  0 continue;
1704    }
1705   
1706  0 for (int seqNo = 0; seqNo < seqs[pdbfnum].length; seqNo++)
1707    {
1708  0 for (int m = 0; m < mapping.length; m++)
1709    {
1710  0 final SequenceI seq = seqs[pdbfnum][seqNo];
1711  0 int sp = alignment.findIndex(seq);
1712  0 StructureMapping structureMapping = mapping[m];
1713  0 if (structureMapping.getSequence() == seq && sp > -1)
1714    {
1715    /*
1716    * found a sequence with a mapping to a structure;
1717    * now scan its features
1718    */
1719  0 if (!visibleFeatures.isEmpty())
1720    {
1721  0 scanSequenceFeatures(visibleFeatures, structureMapping, seq,
1722    theMap, modelId);
1723    }
1724  0 if (showLinkedFeatures)
1725    {
1726  0 scanComplementFeatures(complementRenderer, structureMapping,
1727    seq, theMap, modelId);
1728    }
1729    }
1730    }
1731    }
1732    }
1733  0 return theMap;
1734    }
1735   
1736    /**
1737    * Ask the structure viewer to open a session file. Returns true if
1738    * successful, else false (or not supported).
1739    *
1740    * @param filepath
1741    * @return
1742    */
 
1743  0 toggle public boolean openSession(String filepath)
1744    {
1745  0 StructureCommandI cmd = getCommandGenerator().openSession(filepath);
1746  0 if (cmd == null)
1747    {
1748  0 return false;
1749    }
1750  0 executeCommand(cmd, true);
1751    // todo: test for failure - how?
1752  0 return true;
1753    }
1754   
1755    /**
1756    * Scans visible features in mapped positions of the CDS/peptide complement,
1757    * and adds any found to the map of attribute values/structure positions
1758    *
1759    * @param complementRenderer
1760    * @param structureMapping
1761    * @param seq
1762    * @param theMap
1763    * @param modelNumber
1764    */
 
1765  0 toggle protected static void scanComplementFeatures(
1766    FeatureRenderer complementRenderer,
1767    StructureMapping structureMapping, SequenceI seq,
1768    Map<String, Map<Object, AtomSpecModel>> theMap,
1769    String modelNumber)
1770    {
1771    /*
1772    * for each sequence residue mapped to a structure position...
1773    */
1774  0 for (int seqPos : structureMapping.getMapping().keySet())
1775    {
1776    /*
1777    * find visible complementary features at mapped position(s)
1778    */
1779  0 MappedFeatures mf = complementRenderer
1780    .findComplementFeaturesAtResidue(seq, seqPos);
1781  0 if (mf != null)
1782    {
1783  0 for (SequenceFeature sf : mf.features)
1784    {
1785  0 String type = sf.getType();
1786   
1787    /*
1788    * Don't copy features which originated from Chimera
1789    */
1790  0 if (JalviewChimeraBinding.CHIMERA_FEATURE_GROUP
1791    .equals(sf.getFeatureGroup()))
1792    {
1793  0 continue;
1794    }
1795   
1796    /*
1797    * record feature 'value' (score/description/type) as at the
1798    * corresponding structure position
1799    */
1800  0 List<int[]> mappedRanges = structureMapping
1801    .getPDBResNumRanges(seqPos, seqPos);
1802   
1803  0 if (!mappedRanges.isEmpty())
1804    {
1805  0 String value = sf.getDescription();
1806  0 if (value == null || value.length() == 0)
1807    {
1808  0 value = type;
1809    }
1810  0 float score = sf.getScore();
1811  0 if (score != 0f && !Float.isNaN(score))
1812    {
1813  0 value = Float.toString(score);
1814    }
1815  0 Map<Object, AtomSpecModel> featureValues = theMap.get(type);
1816  0 if (featureValues == null)
1817    {
1818  0 featureValues = new HashMap<>();
1819  0 theMap.put(type, featureValues);
1820    }
1821  0 for (int[] range : mappedRanges)
1822    {
1823  0 addAtomSpecRange(featureValues, value, modelNumber, range[0],
1824    range[1], structureMapping.getChain());
1825    }
1826    }
1827    }
1828    }
1829    }
1830    }
1831   
1832    /**
1833    * Inspect features on the sequence; for each feature that is visible,
1834    * determine its mapped ranges in the structure (if any) according to the
1835    * given mapping, and add them to the map.
1836    *
1837    * @param visibleFeatures
1838    * @param mapping
1839    * @param seq
1840    * @param theMap
1841    * @param modelId
1842    */
 
1843  0 toggle protected static void scanSequenceFeatures(List<String> visibleFeatures,
1844    StructureMapping mapping, SequenceI seq,
1845    Map<String, Map<Object, AtomSpecModel>> theMap, String modelId)
1846    {
1847  0 List<SequenceFeature> sfs = seq.getFeatures().getPositionalFeatures(
1848    visibleFeatures.toArray(new String[visibleFeatures.size()]));
1849  0 for (SequenceFeature sf : sfs)
1850    {
1851  0 String type = sf.getType();
1852   
1853    /*
1854    * Don't copy features which originated from Chimera
1855    */
1856  0 if (JalviewChimeraBinding.CHIMERA_FEATURE_GROUP
1857    .equals(sf.getFeatureGroup()))
1858    {
1859  0 continue;
1860    }
1861   
1862  0 List<int[]> mappedRanges = mapping.getPDBResNumRanges(sf.getBegin(),
1863    sf.getEnd());
1864   
1865  0 if (!mappedRanges.isEmpty())
1866    {
1867  0 String value = sf.getDescription();
1868  0 if (value == null || value.length() == 0)
1869    {
1870  0 value = type;
1871    }
1872  0 float score = sf.getScore();
1873  0 if (score != 0f && !Float.isNaN(score))
1874    {
1875  0 value = Float.toString(score);
1876    }
1877  0 Map<Object, AtomSpecModel> featureValues = theMap.get(type);
1878  0 if (featureValues == null)
1879    {
1880  0 featureValues = new HashMap<>();
1881  0 theMap.put(type, featureValues);
1882    }
1883  0 for (int[] range : mappedRanges)
1884    {
1885  0 addAtomSpecRange(featureValues, value, modelId, range[0],
1886    range[1], mapping.getChain());
1887    }
1888    }
1889    }
1890    }
1891   
1892    /**
1893    * Returns the number of structure files in the structure viewer and mapped to
1894    * Jalview. This may be zero if the files are still in the process of loading
1895    * in the viewer.
1896    *
1897    * @return
1898    */
 
1899  11 toggle public int getMappedStructureCount()
1900    {
1901  11 String[] files = getStructureFiles();
1902  11 return files == null ? 0 : files.length;
1903    }
1904   
1905    /**
1906    * Starts a thread that waits for the external viewer program process to
1907    * finish, so that we can then close the associated resources. This avoids
1908    * leaving orphaned viewer panels in Jalview if the user closes the external
1909    * viewer.
1910    *
1911    * @param p
1912    */
 
1913  0 toggle protected void startExternalViewerMonitor(Process p)
1914    {
1915  0 externalViewerMonitor = new Thread(new Runnable()
1916    {
1917   
 
1918  0 toggle @Override
1919    public void run()
1920    {
1921  0 try
1922    {
1923  0 p.waitFor();
1924  0 JalviewStructureDisplayI display = getViewer();
1925  0 if (display != null)
1926    {
1927  0 display.closeViewer(false);
1928    }
1929    } catch (InterruptedException e)
1930    {
1931    // exit thread if Chimera Viewer is closed in Jalview
1932    }
1933    }
1934    });
1935  0 externalViewerMonitor.start();
1936    }
1937   
1938    /**
1939    * If supported by the external structure viewer, sends it commands to notify
1940    * model or selection changes to the specified URL (where Jalview has started
1941    * a listener)
1942    *
1943    * @param uri
1944    */
 
1945  0 toggle protected void startListening(String uri)
1946    {
1947  0 List<StructureCommandI> commands = getCommandGenerator()
1948    .startNotifications(uri);
1949  0 if (commands != null)
1950    {
1951  0 executeCommands(commands, false, null);
1952    }
1953    }
1954   
1955    /**
1956    * If supported by the external structure viewer, sends it commands to stop
1957    * notifying model or selection changes
1958    */
 
1959  2 toggle protected void stopListening()
1960    {
1961  2 List<StructureCommandI> commands = getCommandGenerator()
1962    .stopNotifications();
1963  2 if (commands != null)
1964    {
1965  0 executeCommands(commands, false, null);
1966    }
1967    }
1968   
1969    /**
1970    * If supported by the structure viewer, queries it for all residue attributes
1971    * with the given attribute name, and creates features on corresponding
1972    * residues of the alignment. Returns the number of features added.
1973    *
1974    * @param attName
1975    * @param alignmentPanel
1976    * @return
1977    */
 
1978  0 toggle public int copyStructureAttributesToFeatures(String attName,
1979    AlignmentPanel alignmentPanel)
1980    {
1981  0 StructureCommandI cmd = getCommandGenerator()
1982    .getResidueAttributes(attName);
1983  0 if (cmd == null)
1984    {
1985  0 return 0;
1986    }
1987  0 List<String> residueAttributes = executeCommand(cmd, true);
1988   
1989  0 int featuresAdded = createFeaturesForAttributes(attName,
1990    residueAttributes);
1991  0 if (featuresAdded > 0)
1992    {
1993  0 alignmentPanel.getFeatureRenderer().featuresAdded();
1994    }
1995  0 return featuresAdded;
1996    }
1997   
1998    /**
1999    * Parses {@code residueAttributes} and creates sequence features on any
2000    * mapped alignment residues. Returns the number of features created.
2001    * <p>
2002    * {@code residueAttributes} is the reply from the structure viewer to a
2003    * command to list any residue attributes for the given attribute name. Syntax
2004    * and parsing of this is viewer-specific.
2005    *
2006    * @param attName
2007    * @param residueAttributes
2008    * @return
2009    */
 
2010  0 toggle protected int createFeaturesForAttributes(String attName,
2011    List<String> residueAttributes)
2012    {
2013  0 return 0;
2014    }
2015   
2016    /**
2017    * list the ligands available for display/hiding in the current view
2018    *
2019    * @return HETATM CODE:Molecule name
2020    */
 
2021  0 toggle public Map<String, String> getHetatmNames()
2022    {
2023  0 return Collections.EMPTY_MAP;
2024    }
2025   
2026    /**
2027    * Generates and executes a command to show the given hetatm types as CPK
2028    *
2029    * @param toShow
2030    * - one or more of strings from getHetatmNames
2031    */
 
2032  0 toggle public void showHetatms(List<String> toShow)
2033    {
2034  0 executeCommands(commandGenerator.showHetatms(toShow), false,
2035    "Adjusting hetatm visibility");
2036    }
2037   
2038    }