Clover icon

Coverage Report

  1. Project Clover database Wed Nov 12 2025 13:01:44 GMT
  2. Package jalview.structures.models

File AAStructureBindingModel.java

 

Coverage histogram

../../../img/srcFileCovDistChart7.png
30% of files have more coverage

Code metrics

262
521
81
2
2,053
1,274
256
0.49
6.43
40.5
3.16

Classes

Class Line # Actions
AAStructureBindingModel 81 519 255
0.6202090462%
AAStructureBindingModel.SuperposeData 88 2 1
1.0100%
 

Contributing tests

This file is covered by 59 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  15 toggle public SuperposeData(int width, String model)
118    {
119  15 pdbResNo = new int[width];
120  15 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  68 toggle public AAStructureBindingModel(StructureSelectionManager ssm,
198    SequenceI[][] seqs)
199    {
200  68 this.ssm = ssm;
201  68 this.sequence = seqs;
202  68 chainNames = new ArrayList<>();
203  68 chainFile = new HashMap<>();
204    }
205   
206    /**
207    * Constructor
208    *
209    * @param ssm
210    * @param pdbentry
211    * @param sequenceIs
212    * @param protocol
213    */
 
214  68 toggle public AAStructureBindingModel(StructureSelectionManager ssm,
215    PDBEntry[] pdbentry, SequenceI[][] sequenceIs,
216    DataSourceType protocol)
217    {
218  68 this(ssm, sequenceIs);
219  68 this.nucleotide = Comparison.isNucleotide(sequenceIs);
220  68 this.pdbEntry = pdbentry;
221  68 this.protocol = protocol;
222  68 resolveChains();
223    }
224   
 
225  68 toggle private boolean resolveChains()
226    {
227    /**
228    * final count of chain mappings discovered
229    */
230  68 int chainmaps = 0;
231    // JBPNote: JAL-2693 - this should be a list of chain mappings per
232    // [pdbentry][sequence]
233  68 String[][] newchains = new String[pdbEntry.length][];
234  68 int pe = 0;
235  68 for (PDBEntry pdb : pdbEntry)
236    {
237  87 SequenceI[] seqsForPdb = sequence[pe];
238  87 if (seqsForPdb != null)
239    {
240  87 newchains[pe] = new String[seqsForPdb.length];
241  87 int se = 0;
242  87 for (SequenceI asq : seqsForPdb)
243    {
244  99 String chain = (chains != null && chains[pe] != null)
245    ? chains[pe][se]
246    : null;
247  99 SequenceI sq = (asq.getDatasetSequence() == null) ? asq
248    : asq.getDatasetSequence();
249  99 if (sq.getAllPDBEntries() != null)
250    {
251  99 for (PDBEntry pdbentry : sq.getAllPDBEntries())
252    {
253  120 if (pdb.getFile() != null && pdbentry.getFile() != null
254    && pdb.getFile().equals(pdbentry.getFile()))
255    {
256  81 String chaincode = pdbentry.getChainCode();
257  81 if (chaincode != null && chaincode.length() > 0)
258    {
259  68 chain = chaincode;
260  68 chainmaps++;
261  68 break;
262    }
263    }
264    }
265    }
266  99 newchains[pe][se] = chain;
267  99 se++;
268    }
269  87 pe++;
270    }
271    }
272   
273  68 chains = newchains;
274  68 return chainmaps > 0;
275    }
276   
 
277  1316 toggle public StructureSelectionManager getSsm()
278    {
279  1316 return ssm;
280    }
281   
282    /**
283    * Returns the i'th PDBEntry (or null)
284    *
285    * @param i
286    * @return
287    */
 
288  537 toggle public PDBEntry getPdbEntry(int i)
289    {
290  537 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  5 toggle public boolean hasPdbId(String pdbId)
300    {
301  5 if (pdbEntry != null)
302    {
303  5 for (PDBEntry pdb : pdbEntry)
304    {
305  6 if (pdb.getId().equals(pdbId))
306    {
307  0 return true;
308    }
309    }
310    }
311  5 return false;
312    }
313   
314    /**
315    * Returns the number of modelled PDB file entries.
316    *
317    * @return
318    */
 
319  863 toggle public int getPdbCount()
320    {
321  863 return pdbEntry == null ? 0 : pdbEntry.length;
322    }
323   
 
324  1863 toggle public SequenceI[][] getSequence()
325    {
326  1863 return sequence;
327    }
328   
 
329  972 toggle public String[][] getChains()
330    {
331  972 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  5 toggle protected void setPdbentry(PDBEntry[] pdbentry)
341    {
342  5 this.pdbEntry = pdbentry;
343    }
344   
 
345  5 toggle protected void setSequence(SequenceI[][] sequence)
346    {
347  5 this.sequence = sequence;
348    }
349   
 
350  5 toggle protected void setChains(String[][] chains)
351    {
352  5 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  142 toggle public String getDynamicViewerTitle(String viewerName, boolean verbose)
366    {
367  142 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  142 StringBuilder title = new StringBuilder(64);
375  142 final PDBEntry pdbe = getPdbEntry(0);
376  142 title.append(viewerName + " view for " + getSequence()[0][0].getName()
377    + ":" + pdbe.getId());
378   
379  142 if (verbose)
380    {
381  142 String method = (String) pdbe.getProperty("method");
382  142 if (method != null)
383    {
384  0 title.append(" Method: ").append(method);
385    }
386  142 String chain = (String) pdbe.getProperty("chains");
387  142 if (chain != null)
388    {
389  0 title.append(" Chain:").append(chain);
390    }
391    }
392  142 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  224 toggle public boolean isColourBySequence()
409    {
410  224 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  71 toggle public void refreshPdbEntries()
429    {
430    }
431   
 
432  150 toggle public void setColourBySequence(boolean colourBySequence)
433    {
434  150 this.colourBySequence = colourBySequence;
435    }
436   
 
437  163 toggle protected void addSequenceAndChain(int pe, SequenceI[] seq,
438    String[] tchain)
439    {
440  163 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  163 final String nullChain = "TheNullChain";
448  163 List<SequenceI> s = new ArrayList<>();
449  163 List<String> c = new ArrayList<>();
450  163 if (getChains() == null)
451    {
452  0 setChains(new String[getPdbCount()][]);
453    }
454  163 if (getSequence()[pe] != null)
455    {
456  352 for (int i = 0; i < getSequence()[pe].length; i++)
457    {
458  194 s.add(getSequence()[pe][i]);
459  194 if (getChains()[pe] != null)
460    {
461  180 if (i < getChains()[pe].length)
462    {
463  180 c.add(getChains()[pe][i]);
464    }
465    else
466    {
467  0 c.add(nullChain);
468    }
469    }
470    else
471    {
472  14 if (tchain != null && tchain.length > 0)
473    {
474  0 c.add(nullChain);
475    }
476    }
477    }
478    }
479  362 for (int i = 0; i < seq.length; i++)
480    {
481  199 if (!s.contains(seq[i]))
482    {
483  5 s.add(seq[i]);
484  5 if (tchain != null && i < tchain.length)
485    {
486  0 c.add(tchain[i] == null ? nullChain : tchain[i]);
487    }
488    }
489    }
490  163 SequenceI[] tmp = s.toArray(new SequenceI[s.size()]);
491  163 getSequence()[pe] = tmp;
492  163 if (c.size() > 0)
493    {
494  144 String[] tch = c.toArray(new String[c.size()]);
495  324 for (int i = 0; i < tch.length; i++)
496    {
497  180 if (tch[i] == nullChain)
498    {
499  0 tch[i] = null;
500    }
501    }
502  144 getChains()[pe] = tch;
503    }
504    else
505    {
506  19 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  5 toggle public synchronized PDBEntry[] addSequenceAndChain(PDBEntry[] pdbe,
516    SequenceI[][] seq, String[][] chns)
517    {
518  5 List<PDBEntry> v = new ArrayList<>();
519  5 List<int[]> rtn = new ArrayList<>();
520  11 for (int i = 0; i < getPdbCount(); i++)
521    {
522  6 v.add(getPdbEntry(i));
523    }
524  10 for (int i = 0; i < pdbe.length; i++)
525    {
526  5 int r = v.indexOf(pdbe[i]);
527  5 if (r == -1 || r >= getPdbCount())
528    {
529  5 rtn.add(new int[] { v.size(), i });
530  5 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  5 pdbe = v.toArray(new PDBEntry[v.size()]);
539  5 setPdbentry(pdbe);
540  5 if (rtn.size() > 0)
541    {
542    // expand the tied sequence[] and string[] arrays
543  5 SequenceI[][] sqs = new SequenceI[getPdbCount()][];
544  5 String[][] sch = new String[getPdbCount()][];
545  5 System.arraycopy(getSequence(), 0, sqs, 0, getSequence().length);
546  5 System.arraycopy(getChains(), 0, sch, 0, this.getChains().length);
547  5 setSequence(sqs);
548  5 setChains(sch);
549  5 pdbe = new PDBEntry[rtn.size()];
550  10 for (int r = 0; r < pdbe.length; r++)
551    {
552  5 int[] stri = (rtn.get(r));
553    // record the pdb file as a new addition
554  5 pdbe[r] = getPdbEntry(stri[0]);
555    // and add the new sequence/chain entries
556  5 addSequenceAndChain(stri[0], seq[stri[1]], chns[stri[1]]);
557    }
558    }
559    else
560    {
561  0 pdbe = null;
562    }
563  5 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  158 toggle public void addSequence(int pe, SequenceI[] seq)
573    {
574  158 addSequenceAndChain(pe, seq, null);
575    }
576   
577    /**
578    * add the given sequences to the mapping scope for the given pdb file handle
579    *
580    * @param pdbFile
581    * - pdbFile identifier
582    * @param seq
583    * - set of sequences it can be mapped to
584    */
 
585  33 toggle public void addSequenceForStructFile(String pdbFile, SequenceI[] seq)
586    {
587  66 for (int pe = 0; pe < getPdbCount(); pe++)
588    {
589  33 if (getPdbEntry(pe).getFile().equals(pdbFile))
590    {
591  28 addSequence(pe, seq);
592    }
593    }
594    }
595   
596    @Override
597    public abstract void highlightAtoms(List<AtomSpec> atoms);
598   
 
599  0 toggle protected boolean isNucleotide()
600    {
601  0 return this.nucleotide;
602    }
603   
604    /**
605    * Returns a readable description of all mappings for the wrapped pdbfile to
606    * any mapped sequences
607    *
608    * @param pdbfile
609    * @param seqs
610    * @return
611    */
 
612  0 toggle public String printMappings()
613    {
614  0 if (pdbEntry == null)
615    {
616  0 return "";
617    }
618  0 StringBuilder sb = new StringBuilder(128);
619  0 for (int pdbe = 0; pdbe < getPdbCount(); pdbe++)
620    {
621  0 String pdbfile = getPdbEntry(pdbe).getFile();
622  0 List<SequenceI> seqs = Arrays.asList(getSequence()[pdbe]);
623  0 sb.append(getSsm().printMappings(pdbfile, seqs));
624    }
625  0 return sb.toString();
626    }
627   
628    /**
629    * Returns the mapped structure position for a given aligned column of a given
630    * sequence, or -1 if the column is gapped, beyond the end of the sequence, or
631    * not mapped to structure.
632    *
633    * @param seq
634    * @param alignedPos
635    * @param mapping
636    * @return
637    */
 
638  1322 toggle protected int getMappedPosition(SequenceI seq, int alignedPos,
639    StructureMapping mapping)
640    {
641  1322 if (alignedPos >= seq.getLength())
642    {
643  1 return -1;
644    }
645   
646  1321 if (Comparison.isGap(seq.getCharAt(alignedPos)))
647    {
648  126 return -1;
649    }
650  1195 int seqPos = seq.findPosition(alignedPos);
651  1195 int pos = mapping.getPDBResNum(seqPos);
652  1195 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  5 toggle protected int findSuperposableResidues(AlignmentI alignment,
674    BitSet matched,
675    AAStructureBindingModel.SuperposeData[] structures)
676    {
677  5 int refStructure = -1;
678  5 String[] files = getStructureFiles();
679  5 if (files == null)
680    {
681  0 return -1;
682    }
683  19 for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
684    {
685  14 StructureMapping[] mappings = getSsm().getMapping(files[pdbfnum]);
686  14 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  14 final int seqCountForPdbFile = getSequence()[pdbfnum].length;
693  28 for (int s = 0; s < seqCountForPdbFile; s++)
694    {
695  14 for (StructureMapping mapping : mappings)
696    {
697  17 final SequenceI theSequence = getSequence()[pdbfnum][s];
698  17 if (mapping.getSequence() == theSequence
699    && alignment.findIndex(theSequence) > -1)
700    {
701  14 if (refStructure < 0)
702    {
703  5 refStructure = pdbfnum;
704    }
705  1586 for (int r = 0; r < alignment.getWidth(); r++)
706    {
707  1572 if (!matched.get(r))
708    {
709  250 continue;
710    }
711  1322 int pos = getMappedPosition(theSequence, r, mapping);
712  1322 if (pos < 1 || pos == lastPos)
713    {
714  127 matched.clear(r);
715  127 continue;
716    }
717  1195 lastPos = pos;
718  1195 structures[pdbfnum].pdbResNo[r] = pos;
719    }
720  14 String chain = mapping.getChain();
721  14 if (chain != null && chain.trim().length() > 0)
722    {
723  14 structures[pdbfnum].chain = chain;
724    }
725  14 structures[pdbfnum].pdbId = mapping.getPdbId();
726  14 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  14 s = seqCountForPdbFile;
733  14 break; // fixme break out of two loops here!
734    }
735    }
736    }
737    }
738  5 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  3 toggle protected boolean waitForFileLoad(String[] files)
749    {
750    /*
751    * give up after 10 secs plus 1 sec per file
752    */
753  3 long starttime = System.currentTimeMillis();
754  3 long endTime = 10000 + 1000 * files.length + starttime;
755  3 String notLoaded = null;
756   
757  3 boolean waiting = true;
758  6 while (waiting && System.currentTimeMillis() < endTime)
759    {
760  3 waiting = false;
761  3 for (String file : files)
762    {
763  8 notLoaded = file;
764  8 if (file == null)
765    {
766  0 continue;
767    }
768  8 try
769    {
770  8 StructureMapping[] sm = getSsm().getMapping(file);
771  8 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  3 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  3 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  866 toggle public boolean isFinishedInit()
816    {
817  866 return finishedInit;
818    }
819   
 
820  124 toggle public void setFinishedInit(boolean fi)
821    {
822  124 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  190 toggle public List<String> getChainNames()
832    {
833  190 return chainNames;
834    }
835   
836    /**
837    * Returns the Jalview panel hosting the structure viewer (if any)
838    *
839    * @return
840    */
 
841  3620 toggle public JalviewStructureDisplayI getViewer()
842    {
843  3620 return viewer;
844    }
845   
 
846  102 toggle public void setViewer(JalviewStructureDisplayI v)
847    {
848  102 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  3 toggle public String superposeStructures(List<AlignmentViewPanel> alignWith)
861    {
862  3 String error = "";
863  3 String[] files = getStructureFiles();
864   
865  3 if (!waitForFileLoad(files))
866    {
867  0 return null;
868    }
869  3 refreshPdbEntries();
870   
871  3 for (AlignmentViewPanel view : alignWith)
872    {
873  3 AlignmentI alignment = view.getAlignment();
874  3 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  3 final int width = alignment.getWidth();
880  3 BitSet matched = new BitSet();
881  3 ColumnSelection cs = view.getAlignViewport().getColumnSelection();
882    // restrict to active column selection, if there is one
883  3 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  614 for (int m = 0; m < width; m++)
894    {
895  611 if (hiddenCols == null || hiddenCols.isVisible(m))
896    {
897  611 matched.set(m);
898    }
899    }
900    }
901  3 AAStructureBindingModel.SuperposeData[] structures = new AAStructureBindingModel.SuperposeData[files.length];
902  11 for (int f = 0; f < files.length; f++)
903    {
904  8 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  3 int refStructure = findSuperposableResidues(alignment, matched,
913    structures);
914   
915    /*
916    * require at least 4 positions to be able to execute superposition
917    */
918  3 int nmatched = matched.cardinality();
919  3 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  3 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  3 executeCommands(commandGenerator.showBackbone(), true, null);
939   
940  3 AtomSpecType backbone = structures[refStructure].isRna
941    ? AtomSpecType.PHOSPHATE
942    : AtomSpecType.ALPHA;
943  3 List<AtomSpecModel> models = new ArrayList<AtomSpecModel>();
944  3 models.add(refAtoms);
945    /*
946    * superpose each (other) structure to the reference in turn
947    */
948  11 for (int i = 0; i < structures.length; i++)
949    {
950  8 if (i != refStructure)
951    {
952  5 AtomSpecModel atomSpec = getAtomSpec(structures[i], matched);
953  5 List<StructureCommandI> commands = commandGenerator
954    .superposeStructures(refAtoms, atomSpec, backbone);
955  5 List<String> replies = executeCommands(commands, true, null);
956  5 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  5 models.add(atomSpec);
966    }
967    }
968  3 List<StructureCommandI> finalView = commandGenerator
969    .centerViewOn(models);
970  3 if (finalView != null && finalView.size() > 0)
971    {
972  3 executeCommands(finalView, false, "Centered on Superposition");
973    }
974    }
975  3 return error;
976    }
977   
 
978  8 toggle private AtomSpecModel getAtomSpec(
979    AAStructureBindingModel.SuperposeData superposeData,
980    BitSet matched)
981    {
982  8 AtomSpecModel model = new AtomSpecModel();
983  8 int nextColumnMatch = matched.nextSetBit(0);
984  1182 while (nextColumnMatch != -1)
985    {
986  1174 int pdbResNum = superposeData.pdbResNo[nextColumnMatch];
987  1174 model.addRange(superposeData.modelId, pdbResNum, pdbResNum,
988    superposeData.chain);
989  1174 nextColumnMatch = matched.nextSetBit(nextColumnMatch + 1);
990    }
991   
992  8 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  538 toggle public List<String> executeCommands(List<StructureCommandI> commands,
1096    boolean getReply, String msg)
1097    {
1098  538 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  538 toggle protected List<String> executeCommand(boolean getReply, String msg,
1119    StructureCommandI... cmds)
1120    {
1121  538 JalviewStructureDisplayI theViewer = getViewer();
1122  538 final long handle = msg == null ? 0 : theViewer.startProgressBar(msg);
1123   
1124  538 if (getReply)
1125    {
1126    /*
1127    * execute and wait for reply
1128    */
1129  8 List<String> response = new ArrayList<>();
1130  8 try
1131    {
1132  8 for (StructureCommandI cmd : cmds)
1133    {
1134  8 List<String> replies = executeCommand(cmd, true);
1135  8 if (replies != null)
1136    {
1137  0 response.addAll(replies);
1138    }
1139    }
1140  8 return response;
1141    } finally
1142    {
1143  8 if (msg != null)
1144    {
1145  0 theViewer.stopProgressBar(null, handle);
1146    }
1147    }
1148    }
1149   
1150    /*
1151    * fire and forget
1152    */
1153  530 String threadName = msg == null ? "StructureCommand" : msg;
1154  530 new Thread(new Runnable()
1155    {
 
1156  530 toggle @Override
1157    public void run()
1158    {
1159  530 try
1160    {
1161  530 for (StructureCommandI cmd : cmds)
1162    {
1163  520 executeCommand(cmd, false);
1164    }
1165    } finally
1166    {
1167  530 if (msg != null)
1168    {
1169  520 SwingUtilities.invokeLater(new Runnable()
1170    {
 
1171  519 toggle @Override
1172    public void run()
1173    {
1174  519 theViewer.stopProgressBar(null, handle);
1175    }
1176    });
1177    }
1178    }
1179    }
1180    }, threadName).start();
1181  530 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  535 toggle public void colourBySequence(AlignmentViewPanel alignmentv)
1189    {
1190  535 if (!colourBySequence || !isLoadingFinished() || getSsm() == null)
1191    {
1192  20 return;
1193    }
1194  515 Map<Object, AtomSpecModel> colourMap = buildColoursMap(ssm, sequence,
1195    alignmentv);
1196   
1197  507 List<StructureCommandI> colourBySequenceCommands = commandGenerator
1198    .colourBySequence(colourMap);
1199  507 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  10 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  10 List<String> showThese = new ArrayList<>();
1227  10 for (String chainId : toShow)
1228    {
1229  9 String[] tokens = chainId.split("\\:");
1230  9 if (tokens.length == 2)
1231    {
1232  9 String pdbFile = getFileForChain(chainId);
1233  9 String model = getModelIdForFile(pdbFile);
1234  9 showThese.add(model + ":" + tokens[1]);
1235    }
1236    }
1237  10 executeCommands(commandGenerator.showChains(showThese), false, null);
1238    }
1239   
 
1240  10 toggle public void showAllChains()
1241    {
1242  10 showChains(getChainNames());
1243    }
1244   
1245    /**
1246    * Answers the structure viewer's model id given a PDB file name. Returns an
1247    * empty string if model id is not found.
1248    *
1249    * @param chainId
1250    * @return
1251    */
1252    protected abstract String getModelIdForFile(String chainId);
1253   
 
1254  791 toggle public boolean hasFileLoadingError()
1255    {
1256  791 return fileLoadingError != null && fileLoadingError.length() > 0;
1257    }
1258   
1259    /**
1260    * Returns the FeatureRenderer for the given alignment view
1261    *
1262    * @param avp
1263    * @return
1264    */
 
1265  65 toggle public FeatureRenderer getFeatureRenderer(AlignmentViewPanel avp)
1266    {
1267  65 AlignmentViewPanel ap = (avp == null) ? getViewer().getAlignmentPanel()
1268    : avp;
1269  63 if (ap == null)
1270    {
1271  0 return null;
1272    }
1273  63 return ap.getFeatureRenderer();
1274    }
1275   
 
1276  62 toggle protected void setStructureCommands(StructureCommandsI cmd)
1277    {
1278  62 commandGenerator = cmd;
1279    }
1280   
1281    /**
1282    * Records association of one chain id (formatted as "pdbid:chainCode") with
1283    * the corresponding PDB file name
1284    *
1285    * @param chainId
1286    * @param fileName
1287    */
 
1288  100 toggle public void addChainFile(String chainId, String fileName)
1289    {
1290  100 chainFile.put(chainId, fileName);
1291    }
1292   
1293    /**
1294    * Returns the PDB filename for the given chain id (formatted as
1295    * "pdbid:chainCode"), or null if not found
1296    *
1297    * @param chainId
1298    * @return
1299    */
 
1300  9 toggle protected String getFileForChain(String chainId)
1301    {
1302  9 return chainFile.get(chainId);
1303    }
1304   
 
1305  1444 toggle @Override
1306    public void updateColours(Object source)
1307    {
1308  1444 if (getViewer() == null)
1309    {
1310    // can happen if a viewer was not instantiated or cleaned up and is still
1311    // registered - mostly during tests
1312  51 return;
1313    }
1314  1393 AlignmentViewPanel ap = (AlignmentViewPanel) source;
1315    // ignore events from panels not used to colour this view
1316  1393 if (!getViewer().isUsedForColourBy(ap))
1317    {
1318  929 return;
1319    }
1320  464 if (!isLoadingFromArchive())
1321    {
1322  464 colourBySequence(ap);
1323    }
1324    }
1325   
 
1326  48 toggle public StructureCommandsI getCommandGenerator()
1327    {
1328  48 return commandGenerator;
1329    }
1330   
1331    protected abstract ViewerType getViewerType();
1332   
1333    /**
1334    * Builds a data structure which records mapped structure residues for each
1335    * colour. From this we can easily generate the viewer commands for colour by
1336    * sequence. Constructs and returns a map of {@code Color} to
1337    * {@code AtomSpecModel}, where the atomspec model holds
1338    *
1339    * <pre>
1340    * Model ids
1341    * Chains
1342    * Residue positions
1343    * </pre>
1344    *
1345    * Ordering is by order of addition (for colours), natural ordering (for
1346    * models and chains)
1347    *
1348    * @param ssm
1349    * @param sequence
1350    * @param viewPanel
1351    * @return
1352    */
 
1353  516 toggle protected Map<Object, AtomSpecModel> buildColoursMap(
1354    StructureSelectionManager ssm, SequenceI[][] sequence,
1355    AlignmentViewPanel viewPanel)
1356    {
1357  516 String[] files = getStructureFiles();
1358  516 SequenceRenderer sr = getSequenceRenderer(viewPanel);
1359  516 FeatureRenderer fr = viewPanel.getFeatureRenderer();
1360  516 FeatureColourFinder finder = new FeatureColourFinder(fr);
1361  516 AlignViewportI viewport = viewPanel.getAlignViewport();
1362  516 HiddenColumns cs = viewport.getAlignment().getHiddenColumns();
1363  508 AlignmentI al = viewport.getAlignment();
1364  508 Map<Object, AtomSpecModel> colourMap = new LinkedHashMap<>();
1365  508 Color lastColour = null;
1366   
1367  1144 for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
1368    {
1369  636 final String modelId = getModelIdForFile(files[pdbfnum]);
1370  636 StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
1371   
1372  636 if (mapping == null || mapping.length < 1)
1373    {
1374  46 continue;
1375    }
1376   
1377  590 int startPos = -1, lastPos = -1;
1378  590 String lastChain = "";
1379  1181 for (int s = 0; s < sequence[pdbfnum].length; s++)
1380    {
1381  1315 for (int sp, m = 0; m < mapping.length; m++)
1382    {
1383  724 final SequenceI seq = sequence[pdbfnum][s];
1384  ? if (mapping[m].getSequence() == seq
1385    && (sp = al.findIndex(seq)) > -1)
1386    {
1387  581 SequenceI asp = al.getSequenceAt(sp);
1388  123710 for (int r = 0; r < asp.getLength(); r++)
1389    {
1390    // no mapping to gaps in sequence
1391  123139 if (Comparison.isGap(asp.getCharAt(r)))
1392    {
1393  49748 continue;
1394    }
1395  73390 int pos = mapping[m].getPDBResNum(asp.findPosition(r));
1396   
1397  73400 if (pos < 1 || pos == lastPos)
1398    {
1399  18 continue;
1400    }
1401   
1402  73379 Color colour = sr.getResidueColour(seq, r, finder);
1403   
1404    /*
1405    * darker colour for hidden regions
1406    */
1407  73399 if (!cs.isVisible(r))
1408    {
1409  6 colour = Color.GRAY;
1410    }
1411   
1412  73397 final String chain = mapping[m].getChain();
1413   
1414    /*
1415    * Just keep incrementing the end position for this colour range
1416    * _unless_ colour, PDB model or chain has changed, or there is a
1417    * gap in the mapped residue sequence
1418    */
1419  73397 final boolean newColour = !colour.equals(lastColour);
1420  73393 final boolean nonContig = lastPos + 1 != pos;
1421  73390 final boolean newChain = !chain.equals(lastChain);
1422  73388 if (newColour || nonContig || newChain)
1423    {
1424  12399 if (startPos != -1)
1425    {
1426  11818 addAtomSpecRange(colourMap, lastColour, modelId, startPos,
1427    lastPos, lastChain);
1428    }
1429  12399 startPos = pos;
1430    }
1431  73383 lastColour = colour;
1432  73383 lastPos = pos;
1433  73382 lastChain = chain;
1434    }
1435    // final colour range
1436  581 if (lastColour != null)
1437    {
1438  581 addAtomSpecRange(colourMap, lastColour, modelId, startPos,
1439    lastPos, lastChain);
1440    }
1441    // break;
1442    }
1443    }
1444    }
1445    }
1446  508 return colourMap;
1447    }
1448   
1449    /**
1450    * todo better refactoring (map lookup or similar to get viewer structure id)
1451    *
1452    * @param pdbfnum
1453    * @param file
1454    * @return
1455    */
 
1456  0 toggle protected String getModelId(int pdbfnum, String file)
1457    {
1458  0 return String.valueOf(pdbfnum);
1459    }
1460   
1461    /**
1462    * Saves chains, formatted as "pdbId:chainCode", and lookups from this to the
1463    * full PDB file path
1464    *
1465    * @param pdb
1466    * @param file
1467    */
 
1468  81 toggle public void stashFoundChains(StructureFile pdb, String file)
1469    {
1470  181 for (int i = 0; i < pdb.getChains().size(); i++)
1471    {
1472  100 String chid = pdb.getId() + ":" + pdb.getChains().elementAt(i).id;
1473  100 addChainFile(chid, file);
1474  100 getChainNames().add(chid);
1475    }
1476    }
1477   
1478    /**
1479    * Helper method to add one contiguous range to the AtomSpec model for the
1480    * given value (creating the model if necessary). As used by Jalview,
1481    * {@code value} is
1482    * <ul>
1483    * <li>a colour, when building a 'colour structure by sequence' command</li>
1484    * <li>a feature value, when building a 'set Chimera attributes from features'
1485    * command</li>
1486    * </ul>
1487    *
1488    * @param map
1489    * @param value
1490    * @param model
1491    * @param startPos
1492    * @param endPos
1493    * @param chain
1494    */
 
1495  12399 toggle public static final void addAtomSpecRange(Map<Object, AtomSpecModel> map,
1496    Object value, String model, int startPos, int endPos,
1497    String chain)
1498    {
1499    /*
1500    * Get/initialize map of data for the colour
1501    */
1502  12399 AtomSpecModel atomSpec = map.get(value);
1503  12399 if (atomSpec == null)
1504    {
1505  2503 atomSpec = new AtomSpecModel();
1506  2503 map.put(value, atomSpec);
1507    }
1508   
1509  12399 atomSpec.addRange(model, startPos, endPos, chain);
1510    }
1511   
1512    /**
1513    * Returns the file extension (including '.' separator) to use for a saved
1514    * viewer session file. Default is to return null (not supported), override as
1515    * required.
1516    *
1517    * @return
1518    */
 
1519  0 toggle public String getSessionFileExtension()
1520    {
1521  0 return null;
1522    }
1523   
1524    /**
1525    * If supported, saves the state of the structure viewer to a temporary file
1526    * and returns the file. Returns null and logs an error on any failure.
1527    *
1528    * @return
1529    */
 
1530  2 toggle public File saveSession()
1531    {
1532  2 String prefix = getViewerType().toString();
1533  2 String suffix = getSessionFileExtension();
1534  2 File f = null;
1535  2 try
1536    {
1537  2 f = File.createTempFile(prefix, suffix);
1538  2 saveSession(f);
1539    } catch (IOException e)
1540    {
1541  0 Console.error(String.format("Error saving %s session: %s", prefix,
1542    e.toString()));
1543    }
1544   
1545  2 return f;
1546    }
1547   
1548    /**
1549    * Use restoreSession when you want to restore a previously saved sesssion to
1550    * the running viewer instance.
1551    *
1552    * @param absolutePath
1553    */
 
1554  0 toggle public void restoreSession(String absolutePath)
1555    {
1556  0 String prefix = getViewerType().toString();
1557  0 try
1558    {
1559   
1560  0 StructureCommandI cmd = commandGenerator.restoreSession(absolutePath);
1561  0 if (cmd != null)
1562    {
1563  0 executeCommand(cmd, false);
1564    }
1565    } catch (Throwable e)
1566    {
1567  0 Console.error(String.format("Error restoring %s session: %s", prefix,
1568    e.toString()));
1569    }
1570   
1571    }
1572   
1573    /**
1574    * Saves the structure viewer session to the given file
1575    *
1576    * @param f
1577    */
 
1578  2 toggle protected void saveSession(File f)
1579    {
1580  2 try
1581    {
1582  2 Console.trace("Saving session to " + f.getCanonicalPath().toString());
1583    } catch (Exception foo)
1584    {
1585    }
1586  2 ;
1587  2 StructureCommandI cmd = commandGenerator.saveSession(f.getPath());
1588  2 if (cmd != null)
1589    {
1590  2 executeCommand(cmd, true);
1591    }
1592  2 try
1593    {
1594  2 Console.trace(
1595    "Done saving session to " + f.getCanonicalPath().toString());
1596    } catch (Exception foo)
1597    {
1598    }
1599  2 ;
1600    }
1601   
1602    /**
1603    * Returns true if the viewer is an external structure viewer for which the
1604    * process is still alive, else false (for Jmol, or an external viewer which
1605    * the user has independently closed)
1606    *
1607    * @return
1608    */
 
1609  40 toggle public boolean isViewerRunning()
1610    {
1611  40 return false;
1612    }
1613   
1614    /**
1615    * Closes Jalview's structure viewer panel and releases associated resources.
1616    * If it is managing an external viewer program, and {@code forceClose} is
1617    * true, also asks that program to close.
1618    *
1619    * @param forceClose
1620    */
 
1621  40 toggle public void closeViewer(boolean forceClose)
1622    {
1623  40 getSsm().removeStructureViewerListener(this, this.getStructureFiles());
1624  40 releaseUIResources();
1625   
1626    /*
1627    * end the thread that closes this panel if the external viewer closes
1628    */
1629  40 if (externalViewerMonitor != null)
1630    {
1631  0 externalViewerMonitor.interrupt();
1632  0 externalViewerMonitor = null;
1633    }
1634   
1635  40 stopListening();
1636   
1637  40 if (forceClose)
1638    {
1639  1 StructureCommandI cmd = getCommandGenerator().closeViewer();
1640  1 if (cmd != null)
1641    {
1642  0 executeCommand(cmd, false);
1643    }
1644    }
1645    }
1646   
1647    /**
1648    * Returns the URL of a help page for the structure viewer, or null if none is
1649    * known
1650    *
1651    * @return
1652    */
 
1653  0 toggle public String getHelpURL()
1654    {
1655  0 return null;
1656    }
1657   
1658    /**
1659    * <pre>
1660    * Helper method to build a map of
1661    * { featureType, { feature value, AtomSpecModel } }
1662    * </pre>
1663    *
1664    * @param viewPanel
1665    * @return
1666    */
 
1667  0 toggle protected Map<String, Map<Object, AtomSpecModel>> buildFeaturesMap(
1668    AlignmentViewPanel viewPanel)
1669    {
1670  0 Map<String, Map<Object, AtomSpecModel>> theMap = new LinkedHashMap<>();
1671  0 String[] files = getStructureFiles();
1672  0 if (files == null)
1673    {
1674  0 return theMap;
1675    }
1676   
1677  0 FeatureRenderer fr = viewPanel.getFeatureRenderer();
1678  0 if (fr == null)
1679    {
1680  0 return theMap;
1681    }
1682   
1683  0 AlignViewportI viewport = viewPanel.getAlignViewport();
1684  0 List<String> visibleFeatures = fr.getDisplayedFeatureTypes();
1685   
1686    /*
1687    * if alignment is showing features from complement, we also transfer
1688    * these features to the corresponding mapped structure residues
1689    */
1690  0 boolean showLinkedFeatures = viewport.isShowComplementFeatures();
1691  0 List<String> complementFeatures = new ArrayList<>();
1692  0 FeatureRenderer complementRenderer = null;
1693  0 if (showLinkedFeatures)
1694    {
1695  0 AlignViewportI comp = fr.getViewport().getCodingComplement();
1696  0 if (comp != null)
1697    {
1698  0 complementRenderer = Desktop.getAlignFrameFor(comp)
1699    .getFeatureRenderer();
1700  0 complementFeatures = complementRenderer.getDisplayedFeatureTypes();
1701    }
1702    }
1703  0 if (visibleFeatures.isEmpty() && complementFeatures.isEmpty())
1704    {
1705  0 return theMap;
1706    }
1707   
1708  0 AlignmentI alignment = viewPanel.getAlignment();
1709  0 SequenceI[][] seqs = getSequence();
1710   
1711  0 for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
1712    {
1713  0 String modelId = getModelIdForFile(files[pdbfnum]);
1714  0 StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
1715   
1716  0 if (mapping == null || mapping.length < 1)
1717    {
1718  0 continue;
1719    }
1720   
1721  0 for (int seqNo = 0; seqNo < seqs[pdbfnum].length; seqNo++)
1722    {
1723  0 for (int m = 0; m < mapping.length; m++)
1724    {
1725  0 final SequenceI seq = seqs[pdbfnum][seqNo];
1726  0 int sp = alignment.findIndex(seq);
1727  0 StructureMapping structureMapping = mapping[m];
1728  0 if (structureMapping.getSequence() == seq && sp > -1)
1729    {
1730    /*
1731    * found a sequence with a mapping to a structure;
1732    * now scan its features
1733    */
1734  0 if (!visibleFeatures.isEmpty())
1735    {
1736  0 scanSequenceFeatures(visibleFeatures, structureMapping, seq,
1737    theMap, modelId);
1738    }
1739  0 if (showLinkedFeatures)
1740    {
1741  0 scanComplementFeatures(complementRenderer, structureMapping,
1742    seq, theMap, modelId);
1743    }
1744    }
1745    }
1746    }
1747    }
1748  0 return theMap;
1749    }
1750   
1751    /**
1752    * Ask the structure viewer to open a session file. Returns true if
1753    * successful, else false (or not supported).
1754    *
1755    * @param filepath
1756    * @return
1757    */
 
1758  0 toggle public boolean openSession(String filepath)
1759    {
1760  0 StructureCommandI cmd = getCommandGenerator().openSession(filepath);
1761  0 if (cmd == null)
1762    {
1763  0 return false;
1764    }
1765  0 executeCommand(cmd, true);
1766    // todo: test for failure - how?
1767  0 return true;
1768    }
1769   
1770    /**
1771    * Scans visible features in mapped positions of the CDS/peptide complement,
1772    * and adds any found to the map of attribute values/structure positions
1773    *
1774    * @param complementRenderer
1775    * @param structureMapping
1776    * @param seq
1777    * @param theMap
1778    * @param modelNumber
1779    */
 
1780  0 toggle protected static void scanComplementFeatures(
1781    FeatureRenderer complementRenderer,
1782    StructureMapping structureMapping, SequenceI seq,
1783    Map<String, Map<Object, AtomSpecModel>> theMap,
1784    String modelNumber)
1785    {
1786    /*
1787    * for each sequence residue mapped to a structure position...
1788    */
1789  0 for (int seqPos : structureMapping.getMapping().keySet())
1790    {
1791    /*
1792    * find visible complementary features at mapped position(s)
1793    */
1794  0 MappedFeatures mf = complementRenderer
1795    .findComplementFeaturesAtResidue(seq, seqPos);
1796  0 if (mf != null)
1797    {
1798  0 for (SequenceFeature sf : mf.features)
1799    {
1800  0 String type = sf.getType();
1801   
1802    /*
1803    * Don't copy features which originated from Chimera
1804    */
1805  0 if (JalviewChimeraBinding.CHIMERA_FEATURE_GROUP
1806    .equals(sf.getFeatureGroup()))
1807    {
1808  0 continue;
1809    }
1810   
1811    /*
1812    * record feature 'value' (score/description/type) as at the
1813    * corresponding structure position
1814    */
1815  0 List<int[]> mappedRanges = structureMapping
1816    .getPDBResNumRanges(seqPos, seqPos);
1817   
1818  0 if (!mappedRanges.isEmpty())
1819    {
1820  0 String value = sf.getDescription();
1821  0 if (value == null || value.length() == 0)
1822    {
1823  0 value = type;
1824    }
1825  0 float score = sf.getScore();
1826  0 if (score != 0f && !Float.isNaN(score))
1827    {
1828  0 value = Float.toString(score);
1829    }
1830  0 Map<Object, AtomSpecModel> featureValues = theMap.get(type);
1831  0 if (featureValues == null)
1832    {
1833  0 featureValues = new HashMap<>();
1834  0 theMap.put(type, featureValues);
1835    }
1836  0 for (int[] range : mappedRanges)
1837    {
1838  0 addAtomSpecRange(featureValues, value, modelNumber, range[0],
1839    range[1], structureMapping.getChain());
1840    }
1841    }
1842    }
1843    }
1844    }
1845    }
1846   
1847    /**
1848    * Inspect features on the sequence; for each feature that is visible,
1849    * determine its mapped ranges in the structure (if any) according to the
1850    * given mapping, and add them to the map.
1851    *
1852    * @param visibleFeatures
1853    * @param mapping
1854    * @param seq
1855    * @param theMap
1856    * @param modelId
1857    */
 
1858  0 toggle protected static void scanSequenceFeatures(List<String> visibleFeatures,
1859    StructureMapping mapping, SequenceI seq,
1860    Map<String, Map<Object, AtomSpecModel>> theMap, String modelId)
1861    {
1862  0 List<SequenceFeature> sfs = seq.getFeatures().getPositionalFeatures(
1863    visibleFeatures.toArray(new String[visibleFeatures.size()]));
1864  0 for (SequenceFeature sf : sfs)
1865    {
1866  0 String type = sf.getType();
1867   
1868    /*
1869    * Don't copy features which originated from Chimera
1870    */
1871  0 if (JalviewChimeraBinding.CHIMERA_FEATURE_GROUP
1872    .equals(sf.getFeatureGroup()))
1873    {
1874  0 continue;
1875    }
1876   
1877  0 List<int[]> mappedRanges = mapping.getPDBResNumRanges(sf.getBegin(),
1878    sf.getEnd());
1879   
1880  0 if (!mappedRanges.isEmpty())
1881    {
1882  0 String value = sf.getDescription();
1883  0 if (value == null || value.length() == 0)
1884    {
1885  0 value = type;
1886    }
1887  0 float score = sf.getScore();
1888  0 if (score != 0f && !Float.isNaN(score))
1889    {
1890  0 value = Float.toString(score);
1891    }
1892  0 Map<Object, AtomSpecModel> featureValues = theMap.get(type);
1893  0 if (featureValues == null)
1894    {
1895  0 featureValues = new HashMap<>();
1896  0 theMap.put(type, featureValues);
1897    }
1898  0 for (int[] range : mappedRanges)
1899    {
1900  0 addAtomSpecRange(featureValues, value, modelId, range[0],
1901    range[1], mapping.getChain());
1902    }
1903    }
1904    }
1905    }
1906   
1907    /**
1908    * Returns the number of structure files in the structure viewer and mapped to
1909    * Jalview. This may be zero if the files are still in the process of loading
1910    * in the viewer.
1911    *
1912    * @return
1913    */
 
1914  236 toggle public int getMappedStructureCount()
1915    {
1916  236 String[] files = getStructureFiles();
1917  236 return files == null ? 0 : files.length;
1918    }
1919   
1920    /**
1921    * Starts a thread that waits for the external viewer program process to
1922    * finish, so that we can then close the associated resources. This avoids
1923    * leaving orphaned viewer panels in Jalview if the user closes the external
1924    * viewer.
1925    *
1926    * @param p
1927    */
 
1928  0 toggle protected void startExternalViewerMonitor(Process p)
1929    {
1930  0 externalViewerMonitor = new Thread(new Runnable()
1931    {
1932   
 
1933  0 toggle @Override
1934    public void run()
1935    {
1936  0 try
1937    {
1938  0 p.waitFor();
1939  0 JalviewStructureDisplayI display = getViewer();
1940  0 if (display != null)
1941    {
1942  0 display.closeViewer(false);
1943    }
1944    } catch (InterruptedException e)
1945    {
1946    // exit thread if Chimera Viewer is closed in Jalview
1947    }
1948    }
1949    });
1950  0 externalViewerMonitor.start();
1951    }
1952   
1953    /**
1954    * If supported by the external structure viewer, sends it commands to notify
1955    * model or selection changes to the specified URL (where Jalview has started
1956    * a listener)
1957    *
1958    * @param uri
1959    */
 
1960  0 toggle protected void startListening(String uri)
1961    {
1962  0 List<StructureCommandI> commands = getCommandGenerator()
1963    .startNotifications(uri);
1964  0 if (commands != null)
1965    {
1966  0 executeCommands(commands, false, null);
1967    }
1968    }
1969   
1970    /**
1971    * If supported by the external structure viewer, sends it commands to stop
1972    * notifying model or selection changes
1973    */
 
1974  40 toggle protected void stopListening()
1975    {
1976  40 List<StructureCommandI> commands = getCommandGenerator()
1977    .stopNotifications();
1978  40 if (commands != null)
1979    {
1980  0 executeCommands(commands, false, null);
1981    }
1982    }
1983   
1984    /**
1985    * If supported by the structure viewer, queries it for all residue attributes
1986    * with the given attribute name, and creates features on corresponding
1987    * residues of the alignment. Returns the number of features added.
1988    *
1989    * @param attName
1990    * @param alignmentPanel
1991    * @return
1992    */
 
1993  0 toggle public int copyStructureAttributesToFeatures(String attName,
1994    AlignmentPanel alignmentPanel)
1995    {
1996  0 StructureCommandI cmd = getCommandGenerator()
1997    .getResidueAttributes(attName);
1998  0 if (cmd == null)
1999    {
2000  0 return 0;
2001    }
2002  0 List<String> residueAttributes = executeCommand(cmd, true);
2003   
2004  0 int featuresAdded = createFeaturesForAttributes(attName,
2005    residueAttributes);
2006  0 if (featuresAdded > 0)
2007    {
2008  0 alignmentPanel.getFeatureRenderer().featuresAdded();
2009    }
2010  0 return featuresAdded;
2011    }
2012   
2013    /**
2014    * Parses {@code residueAttributes} and creates sequence features on any
2015    * mapped alignment residues. Returns the number of features created.
2016    * <p>
2017    * {@code residueAttributes} is the reply from the structure viewer to a
2018    * command to list any residue attributes for the given attribute name. Syntax
2019    * and parsing of this is viewer-specific.
2020    *
2021    * @param attName
2022    * @param residueAttributes
2023    * @return
2024    */
 
2025  0 toggle protected int createFeaturesForAttributes(String attName,
2026    List<String> residueAttributes)
2027    {
2028  0 return 0;
2029    }
2030   
2031    /**
2032    * list the ligands available for display/hiding in the current view
2033    *
2034    * @return HETATM CODE:Molecule name
2035    */
 
2036  0 toggle public Map<String, String> getHetatmNames()
2037    {
2038  0 return Collections.EMPTY_MAP;
2039    }
2040   
2041    /**
2042    * Generates and executes a command to show the given hetatm types as CPK
2043    *
2044    * @param toShow
2045    * - one or more of strings from getHetatmNames
2046    */
 
2047  0 toggle public void showHetatms(List<String> toShow)
2048    {
2049  0 executeCommands(commandGenerator.showHetatms(toShow), false,
2050    "Adjusting hetatm visibility");
2051    }
2052   
2053    }