Clover icon

Coverage Report

  1. Project Clover database Mon Dec 8 2025 13:36:16 GMT
  2. Package jalview.structures.models

File AAStructureBindingModel.java

 

Coverage histogram

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

Code metrics

264
528
81
2
2,062
1,284
257
0.49
6.52
40.5
3.17

Classes

Class Line # Actions
AAStructureBindingModel 81 526 256
0.621839162.2%
AAStructureBindingModel.SuperposeData 88 2 1
1.0100%
 

Contributing tests

This file is covered by 57 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  12 toggle public SuperposeData(int width, String model)
118    {
119  12 pdbResNo = new int[width];
120  12 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  1169 toggle public StructureSelectionManager getSsm()
278    {
279  1169 return ssm;
280    }
281   
282    /**
283    * Returns the i'th PDBEntry (or null)
284    *
285    * @param i
286    * @return
287    */
 
288  539 toggle public PDBEntry getPdbEntry(int i)
289    {
290  539 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  865 toggle public int getPdbCount()
320    {
321  865 return pdbEntry == null ? 0 : pdbEntry.length;
322    }
323   
 
324  1866 toggle public SequenceI[][] getSequence()
325    {
326  1866 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  144 toggle public String getDynamicViewerTitle(String viewerName, boolean verbose)
366    {
367  144 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  144 StringBuilder title = new StringBuilder(64);
375  144 final PDBEntry pdbe = getPdbEntry(0);
376  144 title.append(viewerName + " view for " + getSequence()[0][0].getName()
377    + ":" + pdbe.getId());
378   
379  144 if (verbose)
380    {
381  144 String method = (String) pdbe.getProperty("method");
382  144 if (method != null)
383    {
384  0 title.append(" Method: ").append(method);
385    }
386  144 String chain = (String) pdbe.getProperty("chains");
387  144 if (chain != null)
388    {
389  0 title.append(" Chain:").append(chain);
390    }
391    }
392  144 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  226 toggle public boolean isColourBySequence()
409    {
410  226 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  70 toggle public void refreshPdbEntries()
429    {
430    }
431   
 
432  152 toggle public void setColourBySequence(boolean colourBySequence)
433    {
434  152 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  971 toggle protected int getMappedPosition(SequenceI seq, int alignedPos,
639    StructureMapping mapping)
640    {
641  971 if (alignedPos >= seq.getLength())
642    {
643  1 return -1;
644    }
645   
646  970 if (Comparison.isGap(seq.getCharAt(alignedPos)))
647    {
648  66 return -1;
649    }
650  904 int seqPos = seq.findPosition(alignedPos);
651  904 int pos = mapping.getPDBResNum(seqPos);
652  904 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  4 toggle protected int findSuperposableResidues(AlignmentI alignment,
674    BitSet matched,
675    AAStructureBindingModel.SuperposeData[] structures)
676    {
677  4 int refStructure = -1;
678  4 String[] files = getStructureFiles();
679  4 if (files == null)
680    {
681  0 return -1;
682    }
683  15 for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
684    {
685  11 StructureMapping[] mappings = getSsm().getMapping(files[pdbfnum]);
686  11 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  11 final int seqCountForPdbFile = getSequence()[pdbfnum].length;
693  22 for (int s = 0; s < seqCountForPdbFile; s++)
694    {
695  11 for (StructureMapping mapping : mappings)
696    {
697  11 final SequenceI theSequence = getSequence()[pdbfnum][s];
698  11 if (mapping.getSequence() == theSequence
699    && alignment.findIndex(theSequence) > -1)
700    {
701  11 if (refStructure < 0)
702    {
703  4 refStructure = pdbfnum;
704    }
705  1112 for (int r = 0; r < alignment.getWidth(); r++)
706    {
707  1101 if (!matched.get(r))
708    {
709  130 continue;
710    }
711  971 int pos = getMappedPosition(theSequence, r, mapping);
712  971 if (pos < 1 || pos == lastPos)
713    {
714  67 matched.clear(r);
715  67 continue;
716    }
717  904 lastPos = pos;
718  904 structures[pdbfnum].pdbResNo[r] = pos;
719    }
720  11 String chain = mapping.getChain();
721  11 if (chain != null && chain.trim().length() > 0)
722    {
723  11 structures[pdbfnum].chain = chain;
724    }
725  11 structures[pdbfnum].pdbId = mapping.getPdbId();
726  11 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  11 s = seqCountForPdbFile;
733  11 break; // fixme break out of two loops here!
734    }
735    }
736    }
737    }
738  4 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  2 toggle protected boolean waitForFileLoad(String[] files)
749    {
750    /*
751    * give up after 10 secs plus 1 sec per file
752    */
753  2 long starttime = System.currentTimeMillis();
754  2 long endTime = 10000 + 1000 * files.length + starttime;
755  2 String notLoaded = null;
756  2 int numLoaded=0;
757  2 boolean waiting = true;
758  4 while (waiting && System.currentTimeMillis() < endTime)
759    {
760  2 waiting = false;
761  2 int found=0,xtant=0;
762  2 for (String file : files)
763    {
764  5 notLoaded = file;
765  5 if (file == null)
766    {
767  0 continue;
768    }
769  5 xtant++;
770  5 try
771    {
772  5 StructureMapping[] sm = getSsm().getMapping(file);
773  5 if (sm == null || sm.length == 0)
774    {
775  0 waiting = true;
776    } else {
777  5 found++;
778    }
779    } catch (Throwable x)
780    {
781  0 Console.warn("Exception checking for mapping for "+file,x);
782    }
783    }
784  2 if (numLoaded<found)
785    {
786  2 endTime+=500; // some progress! wait longer
787    }
788  2 numLoaded=found;
789    }
790   
791  2 if (waiting)
792    {
793  0 jalview.bin.Console.errPrintln(
794    "Timed out waiting for structure viewer to load file "
795    + notLoaded);
796  0 return false;
797    }
798  2 return true;
799    }
800   
 
801  0 toggle @Override
802    public boolean isListeningFor(SequenceI seq)
803    {
804  0 if (sequence != null)
805    {
806  0 for (SequenceI[] seqs : sequence)
807    {
808  0 if (seqs != null)
809    {
810  0 for (SequenceI s : seqs)
811    {
812  0 if (s == seq || (s.getDatasetSequence() != null
813    && s.getDatasetSequence() == seq.getDatasetSequence()))
814    {
815  0 return true;
816    }
817    }
818    }
819    }
820    }
821  0 return false;
822    }
823   
 
824  822 toggle public boolean isFinishedInit()
825    {
826  822 return finishedInit;
827    }
828   
 
829  124 toggle public void setFinishedInit(boolean fi)
830    {
831  124 this.finishedInit = fi;
832    }
833   
834    /**
835    * Returns a list of chains mapped in this viewer, formatted as
836    * "pdbid:chainCode"
837    *
838    * @return
839    */
 
840  192 toggle public List<String> getChainNames()
841    {
842  192 return chainNames;
843    }
844   
845    /**
846    * Returns the Jalview panel hosting the structure viewer (if any)
847    *
848    * @return
849    */
 
850  2926 toggle public JalviewStructureDisplayI getViewer()
851    {
852  2926 return viewer;
853    }
854   
 
855  102 toggle public void setViewer(JalviewStructureDisplayI v)
856    {
857  102 viewer = v;
858    }
859   
860    /**
861    * Constructs and sends a command to align structures against a reference
862    * structure, based on one or more sequence alignments. May optionally return
863    * an error or warning message for the alignment command(s).
864    *
865    * @param alignWith
866    * an array of one or more alignment views to process
867    * @return
868    */
 
869  2 toggle public String superposeStructures(List<AlignmentViewPanel> alignWith)
870    {
871  2 String error = "";
872  2 String[] files = getStructureFiles();
873   
874  2 if (!waitForFileLoad(files))
875    {
876  0 return null;
877    }
878  2 refreshPdbEntries();
879   
880  2 for (AlignmentViewPanel view : alignWith)
881    {
882  2 AlignmentI alignment = view.getAlignment();
883  2 HiddenColumns hiddenCols = alignment.getHiddenColumns();
884    /*
885    * 'matched' bit i will be set for visible alignment columns i where
886    * all sequences have a residue with a mapping to their PDB structure
887    */
888  2 final int width = alignment.getWidth();
889  2 BitSet matched = new BitSet();
890  2 ColumnSelection cs = view.getAlignViewport().getColumnSelection();
891    // restrict to active column selection, if there is one
892  2 if (cs != null && cs.hasSelectedColumns()
893    && cs.getSelected().size() >= 4)
894    {
895  0 for (int s : cs.getSelected())
896    {
897  0 matched.set(s);
898    }
899    }
900    else
901    {
902  456 for (int m = 0; m < width; m++)
903    {
904  454 if (hiddenCols == null || hiddenCols.isVisible(m))
905    {
906  454 matched.set(m);
907    }
908    }
909    }
910  2 AAStructureBindingModel.SuperposeData[] structures = new AAStructureBindingModel.SuperposeData[files.length];
911  7 for (int f = 0; f < files.length; f++)
912    {
913  5 structures[f] = new AAStructureBindingModel.SuperposeData(width,
914    getModelIdForFile(files[f]));
915    }
916   
917    /*
918    * Calculate the superposable alignment columns ('matched'), and the
919    * corresponding structure residue positions (structures.pdbResNo)
920    */
921  2 int refStructure = findSuperposableResidues(alignment, matched,
922    structures);
923   
924    /*
925    * require at least 4 positions to be able to execute superposition
926    */
927  2 int nmatched = matched.cardinality();
928  2 if (nmatched < MIN_POS_TO_SUPERPOSE)
929    {
930  0 String msg = MessageManager
931    .formatMessage("label.insufficient_residues", nmatched);
932  0 error += view.getViewName() + ": " + msg + "; ";
933  0 continue;
934    }
935   
936    /*
937    * get a model of the superposable residues in the reference structure
938    */
939  2 AtomSpecModel refAtoms = getAtomSpec(structures[refStructure],
940    matched);
941   
942    /*
943    * Show all as backbone before doing superposition(s)
944    * (residues used for matching will be shown as ribbon)
945    */
946    // todo better way to ensure synchronous than setting getReply true!!
947  2 executeCommands(commandGenerator.showBackbone(), true, null);
948   
949  2 AtomSpecType backbone = structures[refStructure].isRna
950    ? AtomSpecType.PHOSPHATE
951    : AtomSpecType.ALPHA;
952  2 List<AtomSpecModel> models = new ArrayList<AtomSpecModel>();
953  2 models.add(refAtoms);
954    /*
955    * superpose each (other) structure to the reference in turn
956    */
957  7 for (int i = 0; i < structures.length; i++)
958    {
959  5 if (i != refStructure)
960    {
961  3 AtomSpecModel atomSpec = getAtomSpec(structures[i], matched);
962  3 List<StructureCommandI> commands = commandGenerator
963    .superposeStructures(refAtoms, atomSpec, backbone);
964  3 List<String> replies = executeCommands(commands, true, null);
965  3 for (String reply : replies)
966    {
967    // return this error (Chimera only) to the user
968  0 if (reply.toLowerCase(Locale.ROOT)
969    .contains("unequal numbers of atoms"))
970    {
971  0 error += "; " + reply;
972    }
973    }
974  3 models.add(atomSpec);
975    }
976    }
977  2 List<StructureCommandI> finalView = commandGenerator
978    .centerViewOn(models);
979  2 if (finalView != null && finalView.size() > 0)
980    {
981  2 executeCommands(finalView, false, "Centered on Superposition");
982    }
983    }
984  2 return error;
985    }
986   
 
987  5 toggle private AtomSpecModel getAtomSpec(
988    AAStructureBindingModel.SuperposeData superposeData,
989    BitSet matched)
990    {
991  5 AtomSpecModel model = new AtomSpecModel();
992  5 int nextColumnMatch = matched.nextSetBit(0);
993  888 while (nextColumnMatch != -1)
994    {
995  883 int pdbResNum = superposeData.pdbResNo[nextColumnMatch];
996  883 model.addRange(superposeData.modelId, pdbResNum, pdbResNum,
997    superposeData.chain);
998  883 nextColumnMatch = matched.nextSetBit(nextColumnMatch + 1);
999    }
1000   
1001  5 return model;
1002    }
1003   
1004    /**
1005    * returns the current sequenceRenderer that should be used to colour the
1006    * structures
1007    *
1008    * @param alignment
1009    *
1010    * @return
1011    */
1012    public abstract SequenceRenderer getSequenceRenderer(
1013    AlignmentViewPanel alignment);
1014   
1015    /**
1016    * Sends a command to the structure viewer to colour each chain with a
1017    * distinct colour (to the extent supported by the viewer)
1018    */
 
1019  0 toggle public void colourByChain()
1020    {
1021  0 colourBySequence = false;
1022   
1023    // TODO: JAL-628 colour chains distinctly across all visible models
1024   
1025  0 executeCommand(false, COLOURING_STRUCTURES,
1026    commandGenerator.colourByChain());
1027    }
1028   
1029    /**
1030    * Sends a command to the structure viewer to colour each chain with a
1031    * distinct colour (to the extent supported by the viewer)
1032    */
 
1033  0 toggle public void colourByCharge()
1034    {
1035  0 colourBySequence = false;
1036   
1037  0 executeCommands(commandGenerator.colourByCharge(), false,
1038    COLOURING_STRUCTURES);
1039    }
1040   
1041    /**
1042    * Sends a command to the structure to apply a colour scheme (defined in
1043    * Jalview but not necessarily applied to the alignment), which defines a
1044    * colour per residue letter. More complex schemes (e.g. that depend on
1045    * consensus) cannot be used here and are ignored.
1046    *
1047    * @param cs
1048    */
 
1049  0 toggle public void colourByJalviewColourScheme(ColourSchemeI cs)
1050    {
1051  0 colourBySequence = false;
1052   
1053  0 if (cs == null || !cs.isSimple())
1054    {
1055  0 return;
1056    }
1057   
1058    /*
1059    * build a map of {Residue3LetterCode, Color}
1060    */
1061  0 Map<String, Color> colours = new HashMap<>();
1062  0 List<String> residues = ResidueProperties.getResidues(isNucleotide(),
1063    false);
1064  0 for (String resName : residues)
1065    {
1066  0 char res = resName.length() == 3
1067    ? ResidueProperties.getSingleCharacterCode(resName)
1068    : resName.charAt(0);
1069  0 Color colour = cs.findColour(res, 0, null, null, 0f);
1070  0 colours.put(resName, colour);
1071    }
1072   
1073    /*
1074    * pass to the command constructor, and send the command
1075    */
1076  0 List<StructureCommandI> cmd = commandGenerator
1077    .colourByResidues(colours);
1078  0 executeCommands(cmd, false, COLOURING_STRUCTURES);
1079    }
1080   
 
1081  0 toggle public void setBackgroundColour(Color col)
1082    {
1083  0 StructureCommandI cmd = commandGenerator.setBackgroundColour(col);
1084  0 executeCommand(false, null, cmd);
1085    }
1086   
1087    /**
1088    * Execute one structure viewer command. If {@code getReply} is true, may
1089    * optionally return one or more reply messages, else returns null.
1090    *
1091    * @param cmd
1092    * @param getReply
1093    */
1094    protected abstract List<String> executeCommand(StructureCommandI cmd,
1095    boolean getReply);
1096   
1097    /**
1098    * Executes one or more structure viewer commands
1099    *
1100    * @param commands
1101    * @param getReply
1102    * @param msg
1103    */
 
1104  455 toggle public List<String> executeCommands(List<StructureCommandI> commands,
1105    boolean getReply, String msg)
1106    {
1107  455 return executeCommand(getReply, msg,
1108    commands.toArray(new StructureCommandI[commands.size()]));
1109    }
1110   
1111    /**
1112    * Executes one or more structure viewer commands, optionally returning the
1113    * reply, and optionally showing a status message while the command is being
1114    * executed.
1115    * <p>
1116    * If a reply is wanted, the execution is done synchronously (waits),
1117    * otherwise it is done in a separate thread (doesn't wait). WARNING: if you
1118    * are sending commands that need to execute before later calls to
1119    * executeCommand (e.g. mouseovers, which clean up after previous ones) then
1120    * set getReply true to ensure that commands are not executed out of order.
1121    *
1122    * @param getReply
1123    * @param msg
1124    * @param cmds
1125    * @return
1126    */
 
1127  455 toggle protected List<String> executeCommand(boolean getReply, String msg,
1128    StructureCommandI... cmds)
1129    {
1130  455 JalviewStructureDisplayI theViewer = getViewer();
1131  455 final long handle = msg == null ? 0 : theViewer.startProgressBar(msg);
1132   
1133  455 if (getReply)
1134    {
1135    /*
1136    * execute and wait for reply
1137    */
1138  5 List<String> response = new ArrayList<>();
1139  5 try
1140    {
1141  5 for (StructureCommandI cmd : cmds)
1142    {
1143  5 List<String> replies = executeCommand(cmd, true);
1144  5 if (replies != null)
1145    {
1146  0 response.addAll(replies);
1147    }
1148    }
1149  5 return response;
1150    } finally
1151    {
1152  5 if (msg != null)
1153    {
1154  0 theViewer.stopProgressBar(null, handle);
1155    }
1156    }
1157    }
1158   
1159    /*
1160    * fire and forget
1161    */
1162  450 String threadName = msg == null ? "StructureCommand" : msg;
1163  450 new Thread(new Runnable()
1164    {
 
1165  450 toggle @Override
1166    public void run()
1167    {
1168  450 try
1169    {
1170  450 for (StructureCommandI cmd : cmds)
1171    {
1172  440 executeCommand(cmd, false);
1173    }
1174    } finally
1175    {
1176  450 if (msg != null)
1177    {
1178  440 SwingUtilities.invokeLater(new Runnable()
1179    {
 
1180  440 toggle @Override
1181    public void run()
1182    {
1183  440 theViewer.stopProgressBar(null, handle);
1184    }
1185    });
1186    }
1187    }
1188    }
1189    }, threadName).start();
1190  450 return null;
1191    }
1192   
1193    /**
1194    * Colours any structures associated with sequences in the given alignment as
1195    * coloured in the alignment view, provided colourBySequence is enabled
1196    */
 
1197  449 toggle public void colourBySequence(AlignmentViewPanel alignmentv)
1198    {
1199  449 if (!colourBySequence || !isLoadingFinished() || getSsm() == null)
1200    {
1201  13 return;
1202    }
1203  436 Map<Object, AtomSpecModel> colourMap = buildColoursMap(ssm, sequence,
1204    alignmentv);
1205   
1206  428 List<StructureCommandI> colourBySequenceCommands = commandGenerator
1207    .colourBySequence(colourMap);
1208  428 executeCommands(colourBySequenceCommands, false, COLOURING_STRUCTURES);
1209    }
1210   
1211    /**
1212    * Centre the display in the structure viewer
1213    */
 
1214  0 toggle public void focusView()
1215    {
1216  0 executeCommand(false, null, commandGenerator.focusView());
1217    }
1218   
1219    /**
1220    * Generates and executes a command to show only specified chains in the
1221    * structure viewer. The list of chains to show should contain entries
1222    * formatted as "pdbid:chaincode".
1223    *
1224    * @param toShow
1225    */
 
1226  10 toggle public void showChains(List<String> toShow)
1227    {
1228    // todo or reformat toShow list entries as modelNo:pdbId:chainCode ?
1229   
1230    /*
1231    * Reformat the pdbid:chainCode values as modelNo:chainCode
1232    * since this is what is needed to construct the viewer command
1233    * todo: find a less messy way to do this
1234    */
1235  10 List<String> showThese = new ArrayList<>();
1236  10 for (String chainId : toShow)
1237    {
1238  9 String[] tokens = chainId.split("\\:");
1239  9 if (tokens.length == 2)
1240    {
1241  9 String pdbFile = getFileForChain(chainId);
1242  9 String model = getModelIdForFile(pdbFile);
1243  9 showThese.add(model + ":" + tokens[1]);
1244    }
1245    }
1246  10 executeCommands(commandGenerator.showChains(showThese), false, null);
1247    }
1248   
 
1249  10 toggle public void showAllChains()
1250    {
1251  10 showChains(getChainNames());
1252    }
1253   
1254    /**
1255    * Answers the structure viewer's model id given a PDB file name. Returns an
1256    * empty string if model id is not found.
1257    *
1258    * @param chainId
1259    * @return
1260    */
1261    protected abstract String getModelIdForFile(String chainId);
1262   
 
1263  758 toggle public boolean hasFileLoadingError()
1264    {
1265  758 return fileLoadingError != null && fileLoadingError.length() > 0;
1266    }
1267   
1268    /**
1269    * Returns the FeatureRenderer for the given alignment view
1270    *
1271    * @param avp
1272    * @return
1273    */
 
1274  65 toggle public FeatureRenderer getFeatureRenderer(AlignmentViewPanel avp)
1275    {
1276  65 AlignmentViewPanel ap = (avp == null) ? getViewer().getAlignmentPanel()
1277    : avp;
1278  63 if (ap == null)
1279    {
1280  0 return null;
1281    }
1282  63 return ap.getFeatureRenderer();
1283    }
1284   
 
1285  62 toggle protected void setStructureCommands(StructureCommandsI cmd)
1286    {
1287  62 commandGenerator = cmd;
1288    }
1289   
1290    /**
1291    * Records association of one chain id (formatted as "pdbid:chainCode") with
1292    * the corresponding PDB file name
1293    *
1294    * @param chainId
1295    * @param fileName
1296    */
 
1297  100 toggle public void addChainFile(String chainId, String fileName)
1298    {
1299  100 chainFile.put(chainId, fileName);
1300    }
1301   
1302    /**
1303    * Returns the PDB filename for the given chain id (formatted as
1304    * "pdbid:chainCode"), or null if not found
1305    *
1306    * @param chainId
1307    * @return
1308    */
 
1309  9 toggle protected String getFileForChain(String chainId)
1310    {
1311  9 return chainFile.get(chainId);
1312    }
1313   
 
1314  1129 toggle @Override
1315    public void updateColours(Object source)
1316    {
1317  1129 if (getViewer() == null)
1318    {
1319    // can happen if a viewer was not instantiated or cleaned up and is still
1320    // registered - mostly during tests
1321  34 return;
1322    }
1323  1095 AlignmentViewPanel ap = (AlignmentViewPanel) source;
1324    // ignore events from panels not used to colour this view
1325  1095 if (!getViewer().isUsedForColourBy(ap))
1326    {
1327  719 return;
1328    }
1329  376 if (!isLoadingFromArchive())
1330    {
1331  376 colourBySequence(ap);
1332    }
1333    }
1334   
 
1335  48 toggle public StructureCommandsI getCommandGenerator()
1336    {
1337  48 return commandGenerator;
1338    }
1339   
1340    protected abstract ViewerType getViewerType();
1341   
1342    /**
1343    * Builds a data structure which records mapped structure residues for each
1344    * colour. From this we can easily generate the viewer commands for colour by
1345    * sequence. Constructs and returns a map of {@code Color} to
1346    * {@code AtomSpecModel}, where the atomspec model holds
1347    *
1348    * <pre>
1349    * Model ids
1350    * Chains
1351    * Residue positions
1352    * </pre>
1353    *
1354    * Ordering is by order of addition (for colours), natural ordering (for
1355    * models and chains)
1356    *
1357    * @param ssm
1358    * @param sequence
1359    * @param viewPanel
1360    * @return
1361    */
 
1362  437 toggle protected Map<Object, AtomSpecModel> buildColoursMap(
1363    StructureSelectionManager ssm, SequenceI[][] sequence,
1364    AlignmentViewPanel viewPanel)
1365    {
1366  437 String[] files = getStructureFiles();
1367  437 SequenceRenderer sr = getSequenceRenderer(viewPanel);
1368  437 FeatureRenderer fr = viewPanel.getFeatureRenderer();
1369  437 FeatureColourFinder finder = new FeatureColourFinder(fr);
1370  437 AlignViewportI viewport = viewPanel.getAlignViewport();
1371  437 HiddenColumns cs = viewport.getAlignment().getHiddenColumns();
1372  429 AlignmentI al = viewport.getAlignment();
1373  429 Map<Object, AtomSpecModel> colourMap = new LinkedHashMap<>();
1374  429 Color lastColour = null;
1375   
1376  974 for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
1377    {
1378  545 final String modelId = getModelIdForFile(files[pdbfnum]);
1379  545 StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
1380   
1381  545 if (mapping == null || mapping.length < 1)
1382    {
1383  49 continue;
1384    }
1385   
1386  496 int startPos = -1, lastPos = -1;
1387  496 String lastChain = "";
1388  992 for (int s = 0; s < sequence[pdbfnum].length; s++)
1389    {
1390  1097 for (int sp, m = 0; m < mapping.length; m++)
1391    {
1392  601 final SequenceI seq = sequence[pdbfnum][s];
1393  ? if (mapping[m].getSequence() == seq
1394    && (sp = al.findIndex(seq)) > -1)
1395    {
1396  487 SequenceI asp = al.getSequenceAt(sp);
1397  102868 for (int r = 0; r < asp.getLength(); r++)
1398    {
1399    // no mapping to gaps in sequence
1400  102420 if (Comparison.isGap(asp.getCharAt(r)))
1401    {
1402  41212 continue;
1403    }
1404  61208 int pos = mapping[m].getPDBResNum(asp.findPosition(r));
1405   
1406  61239 if (pos < 1 || pos == lastPos)
1407    {
1408  14 continue;
1409    }
1410   
1411  61223 Color colour = sr.getResidueColour(seq, r, finder);
1412   
1413    /*
1414    * darker colour for hidden regions
1415    */
1416  61242 if (!cs.isVisible(r))
1417    {
1418  6 colour = Color.GRAY;
1419    }
1420   
1421  61242 final String chain = mapping[m].getChain();
1422   
1423    /*
1424    * Just keep incrementing the end position for this colour range
1425    * _unless_ colour, PDB model or chain has changed, or there is a
1426    * gap in the mapped residue sequence
1427    */
1428  61242 final boolean newColour = !colour.equals(lastColour);
1429  61238 final boolean nonContig = lastPos + 1 != pos;
1430  61230 final boolean newChain = !chain.equals(lastChain);
1431  61221 if (newColour || nonContig || newChain)
1432    {
1433  9613 if (startPos != -1)
1434    {
1435  9126 addAtomSpecRange(colourMap, lastColour, modelId, startPos,
1436    lastPos, lastChain);
1437    }
1438  9613 startPos = pos;
1439    }
1440  61217 lastColour = colour;
1441  61217 lastPos = pos;
1442  61214 lastChain = chain;
1443    }
1444    // final colour range
1445  487 if (lastColour != null)
1446    {
1447  487 addAtomSpecRange(colourMap, lastColour, modelId, startPos,
1448    lastPos, lastChain);
1449    }
1450    // break;
1451    }
1452    }
1453    }
1454    }
1455  429 return colourMap;
1456    }
1457   
1458    /**
1459    * todo better refactoring (map lookup or similar to get viewer structure id)
1460    *
1461    * @param pdbfnum
1462    * @param file
1463    * @return
1464    */
 
1465  0 toggle protected String getModelId(int pdbfnum, String file)
1466    {
1467  0 return String.valueOf(pdbfnum);
1468    }
1469   
1470    /**
1471    * Saves chains, formatted as "pdbId:chainCode", and lookups from this to the
1472    * full PDB file path
1473    *
1474    * @param pdb
1475    * @param file
1476    */
 
1477  81 toggle public void stashFoundChains(StructureFile pdb, String file)
1478    {
1479  181 for (int i = 0; i < pdb.getChains().size(); i++)
1480    {
1481  100 String chid = pdb.getId() + ":" + pdb.getChains().elementAt(i).id;
1482  100 addChainFile(chid, file);
1483  100 getChainNames().add(chid);
1484    }
1485    }
1486   
1487    /**
1488    * Helper method to add one contiguous range to the AtomSpec model for the
1489    * given value (creating the model if necessary). As used by Jalview,
1490    * {@code value} is
1491    * <ul>
1492    * <li>a colour, when building a 'colour structure by sequence' command</li>
1493    * <li>a feature value, when building a 'set Chimera attributes from features'
1494    * command</li>
1495    * </ul>
1496    *
1497    * @param map
1498    * @param value
1499    * @param model
1500    * @param startPos
1501    * @param endPos
1502    * @param chain
1503    */
 
1504  9613 toggle public static final void addAtomSpecRange(Map<Object, AtomSpecModel> map,
1505    Object value, String model, int startPos, int endPos,
1506    String chain)
1507    {
1508    /*
1509    * Get/initialize map of data for the colour
1510    */
1511  9613 AtomSpecModel atomSpec = map.get(value);
1512  9612 if (atomSpec == null)
1513    {
1514  1974 atomSpec = new AtomSpecModel();
1515  1974 map.put(value, atomSpec);
1516    }
1517   
1518  9612 atomSpec.addRange(model, startPos, endPos, chain);
1519    }
1520   
1521    /**
1522    * Returns the file extension (including '.' separator) to use for a saved
1523    * viewer session file. Default is to return null (not supported), override as
1524    * required.
1525    *
1526    * @return
1527    */
 
1528  0 toggle public String getSessionFileExtension()
1529    {
1530  0 return null;
1531    }
1532   
1533    /**
1534    * If supported, saves the state of the structure viewer to a temporary file
1535    * and returns the file. Returns null and logs an error on any failure.
1536    *
1537    * @return
1538    */
 
1539  2 toggle public File saveSession()
1540    {
1541  2 String prefix = getViewerType().toString();
1542  2 String suffix = getSessionFileExtension();
1543  2 File f = null;
1544  2 try
1545    {
1546  2 f = File.createTempFile(prefix, suffix);
1547  2 saveSession(f);
1548    } catch (IOException e)
1549    {
1550  0 Console.error(String.format("Error saving %s session: %s", prefix,
1551    e.toString()));
1552    }
1553   
1554  2 return f;
1555    }
1556   
1557    /**
1558    * Use restoreSession when you want to restore a previously saved sesssion to
1559    * the running viewer instance.
1560    *
1561    * @param absolutePath
1562    */
 
1563  0 toggle public void restoreSession(String absolutePath)
1564    {
1565  0 String prefix = getViewerType().toString();
1566  0 try
1567    {
1568   
1569  0 StructureCommandI cmd = commandGenerator.restoreSession(absolutePath);
1570  0 if (cmd != null)
1571    {
1572  0 executeCommand(cmd, false);
1573    }
1574    } catch (Throwable e)
1575    {
1576  0 Console.error(String.format("Error restoring %s session: %s", prefix,
1577    e.toString()));
1578    }
1579   
1580    }
1581   
1582    /**
1583    * Saves the structure viewer session to the given file
1584    *
1585    * @param f
1586    */
 
1587  2 toggle protected void saveSession(File f)
1588    {
1589  2 try
1590    {
1591  2 Console.trace("Saving session to " + f.getCanonicalPath().toString());
1592    } catch (Exception foo)
1593    {
1594    }
1595  2 ;
1596  2 StructureCommandI cmd = commandGenerator.saveSession(f.getPath());
1597  2 if (cmd != null)
1598    {
1599  2 executeCommand(cmd, true);
1600    }
1601  2 try
1602    {
1603  2 Console.trace(
1604    "Done saving session to " + f.getCanonicalPath().toString());
1605    } catch (Exception foo)
1606    {
1607    }
1608  2 ;
1609    }
1610   
1611    /**
1612    * Returns true if the viewer is an external structure viewer for which the
1613    * process is still alive, else false (for Jmol, or an external viewer which
1614    * the user has independently closed)
1615    *
1616    * @return
1617    */
 
1618  40 toggle public boolean isViewerRunning()
1619    {
1620  40 return false;
1621    }
1622   
1623    /**
1624    * Closes Jalview's structure viewer panel and releases associated resources.
1625    * If it is managing an external viewer program, and {@code forceClose} is
1626    * true, also asks that program to close.
1627    *
1628    * @param forceClose
1629    */
 
1630  40 toggle public void closeViewer(boolean forceClose)
1631    {
1632  40 getSsm().removeStructureViewerListener(this, this.getStructureFiles());
1633  40 releaseUIResources();
1634   
1635    /*
1636    * end the thread that closes this panel if the external viewer closes
1637    */
1638  40 if (externalViewerMonitor != null)
1639    {
1640  0 externalViewerMonitor.interrupt();
1641  0 externalViewerMonitor = null;
1642    }
1643   
1644  40 stopListening();
1645   
1646  40 if (forceClose)
1647    {
1648  1 StructureCommandI cmd = getCommandGenerator().closeViewer();
1649  1 if (cmd != null)
1650    {
1651  0 executeCommand(cmd, false);
1652    }
1653    }
1654    }
1655   
1656    /**
1657    * Returns the URL of a help page for the structure viewer, or null if none is
1658    * known
1659    *
1660    * @return
1661    */
 
1662  0 toggle public String getHelpURL()
1663    {
1664  0 return null;
1665    }
1666   
1667    /**
1668    * <pre>
1669    * Helper method to build a map of
1670    * { featureType, { feature value, AtomSpecModel } }
1671    * </pre>
1672    *
1673    * @param viewPanel
1674    * @return
1675    */
 
1676  0 toggle protected Map<String, Map<Object, AtomSpecModel>> buildFeaturesMap(
1677    AlignmentViewPanel viewPanel)
1678    {
1679  0 Map<String, Map<Object, AtomSpecModel>> theMap = new LinkedHashMap<>();
1680  0 String[] files = getStructureFiles();
1681  0 if (files == null)
1682    {
1683  0 return theMap;
1684    }
1685   
1686  0 FeatureRenderer fr = viewPanel.getFeatureRenderer();
1687  0 if (fr == null)
1688    {
1689  0 return theMap;
1690    }
1691   
1692  0 AlignViewportI viewport = viewPanel.getAlignViewport();
1693  0 List<String> visibleFeatures = fr.getDisplayedFeatureTypes();
1694   
1695    /*
1696    * if alignment is showing features from complement, we also transfer
1697    * these features to the corresponding mapped structure residues
1698    */
1699  0 boolean showLinkedFeatures = viewport.isShowComplementFeatures();
1700  0 List<String> complementFeatures = new ArrayList<>();
1701  0 FeatureRenderer complementRenderer = null;
1702  0 if (showLinkedFeatures)
1703    {
1704  0 AlignViewportI comp = fr.getViewport().getCodingComplement();
1705  0 if (comp != null)
1706    {
1707  0 complementRenderer = Desktop.getAlignFrameFor(comp)
1708    .getFeatureRenderer();
1709  0 complementFeatures = complementRenderer.getDisplayedFeatureTypes();
1710    }
1711    }
1712  0 if (visibleFeatures.isEmpty() && complementFeatures.isEmpty())
1713    {
1714  0 return theMap;
1715    }
1716   
1717  0 AlignmentI alignment = viewPanel.getAlignment();
1718  0 SequenceI[][] seqs = getSequence();
1719   
1720  0 for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
1721    {
1722  0 String modelId = getModelIdForFile(files[pdbfnum]);
1723  0 StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
1724   
1725  0 if (mapping == null || mapping.length < 1)
1726    {
1727  0 continue;
1728    }
1729   
1730  0 for (int seqNo = 0; seqNo < seqs[pdbfnum].length; seqNo++)
1731    {
1732  0 for (int m = 0; m < mapping.length; m++)
1733    {
1734  0 final SequenceI seq = seqs[pdbfnum][seqNo];
1735  0 int sp = alignment.findIndex(seq);
1736  0 StructureMapping structureMapping = mapping[m];
1737  0 if (structureMapping.getSequence() == seq && sp > -1)
1738    {
1739    /*
1740    * found a sequence with a mapping to a structure;
1741    * now scan its features
1742    */
1743  0 if (!visibleFeatures.isEmpty())
1744    {
1745  0 scanSequenceFeatures(visibleFeatures, structureMapping, seq,
1746    theMap, modelId);
1747    }
1748  0 if (showLinkedFeatures)
1749    {
1750  0 scanComplementFeatures(complementRenderer, structureMapping,
1751    seq, theMap, modelId);
1752    }
1753    }
1754    }
1755    }
1756    }
1757  0 return theMap;
1758    }
1759   
1760    /**
1761    * Ask the structure viewer to open a session file. Returns true if
1762    * successful, else false (or not supported).
1763    *
1764    * @param filepath
1765    * @return
1766    */
 
1767  0 toggle public boolean openSession(String filepath)
1768    {
1769  0 StructureCommandI cmd = getCommandGenerator().openSession(filepath);
1770  0 if (cmd == null)
1771    {
1772  0 return false;
1773    }
1774  0 executeCommand(cmd, true);
1775    // todo: test for failure - how?
1776  0 return true;
1777    }
1778   
1779    /**
1780    * Scans visible features in mapped positions of the CDS/peptide complement,
1781    * and adds any found to the map of attribute values/structure positions
1782    *
1783    * @param complementRenderer
1784    * @param structureMapping
1785    * @param seq
1786    * @param theMap
1787    * @param modelNumber
1788    */
 
1789  0 toggle protected static void scanComplementFeatures(
1790    FeatureRenderer complementRenderer,
1791    StructureMapping structureMapping, SequenceI seq,
1792    Map<String, Map<Object, AtomSpecModel>> theMap,
1793    String modelNumber)
1794    {
1795    /*
1796    * for each sequence residue mapped to a structure position...
1797    */
1798  0 for (int seqPos : structureMapping.getMapping().keySet())
1799    {
1800    /*
1801    * find visible complementary features at mapped position(s)
1802    */
1803  0 MappedFeatures mf = complementRenderer
1804    .findComplementFeaturesAtResidue(seq, seqPos);
1805  0 if (mf != null)
1806    {
1807  0 for (SequenceFeature sf : mf.features)
1808    {
1809  0 String type = sf.getType();
1810   
1811    /*
1812    * Don't copy features which originated from Chimera
1813    */
1814  0 if (JalviewChimeraBinding.CHIMERA_FEATURE_GROUP
1815    .equals(sf.getFeatureGroup()))
1816    {
1817  0 continue;
1818    }
1819   
1820    /*
1821    * record feature 'value' (score/description/type) as at the
1822    * corresponding structure position
1823    */
1824  0 List<int[]> mappedRanges = structureMapping
1825    .getPDBResNumRanges(seqPos, seqPos);
1826   
1827  0 if (!mappedRanges.isEmpty())
1828    {
1829  0 String value = sf.getDescription();
1830  0 if (value == null || value.length() == 0)
1831    {
1832  0 value = type;
1833    }
1834  0 float score = sf.getScore();
1835  0 if (score != 0f && !Float.isNaN(score))
1836    {
1837  0 value = Float.toString(score);
1838    }
1839  0 Map<Object, AtomSpecModel> featureValues = theMap.get(type);
1840  0 if (featureValues == null)
1841    {
1842  0 featureValues = new HashMap<>();
1843  0 theMap.put(type, featureValues);
1844    }
1845  0 for (int[] range : mappedRanges)
1846    {
1847  0 addAtomSpecRange(featureValues, value, modelNumber, range[0],
1848    range[1], structureMapping.getChain());
1849    }
1850    }
1851    }
1852    }
1853    }
1854    }
1855   
1856    /**
1857    * Inspect features on the sequence; for each feature that is visible,
1858    * determine its mapped ranges in the structure (if any) according to the
1859    * given mapping, and add them to the map.
1860    *
1861    * @param visibleFeatures
1862    * @param mapping
1863    * @param seq
1864    * @param theMap
1865    * @param modelId
1866    */
 
1867  0 toggle protected static void scanSequenceFeatures(List<String> visibleFeatures,
1868    StructureMapping mapping, SequenceI seq,
1869    Map<String, Map<Object, AtomSpecModel>> theMap, String modelId)
1870    {
1871  0 List<SequenceFeature> sfs = seq.getFeatures().getPositionalFeatures(
1872    visibleFeatures.toArray(new String[visibleFeatures.size()]));
1873  0 for (SequenceFeature sf : sfs)
1874    {
1875  0 String type = sf.getType();
1876   
1877    /*
1878    * Don't copy features which originated from Chimera
1879    */
1880  0 if (JalviewChimeraBinding.CHIMERA_FEATURE_GROUP
1881    .equals(sf.getFeatureGroup()))
1882    {
1883  0 continue;
1884    }
1885   
1886  0 List<int[]> mappedRanges = mapping.getPDBResNumRanges(sf.getBegin(),
1887    sf.getEnd());
1888   
1889  0 if (!mappedRanges.isEmpty())
1890    {
1891  0 String value = sf.getDescription();
1892  0 if (value == null || value.length() == 0)
1893    {
1894  0 value = type;
1895    }
1896  0 float score = sf.getScore();
1897  0 if (score != 0f && !Float.isNaN(score))
1898    {
1899  0 value = Float.toString(score);
1900    }
1901  0 Map<Object, AtomSpecModel> featureValues = theMap.get(type);
1902  0 if (featureValues == null)
1903    {
1904  0 featureValues = new HashMap<>();
1905  0 theMap.put(type, featureValues);
1906    }
1907  0 for (int[] range : mappedRanges)
1908    {
1909  0 addAtomSpecRange(featureValues, value, modelId, range[0],
1910    range[1], mapping.getChain());
1911    }
1912    }
1913    }
1914    }
1915   
1916    /**
1917    * Returns the number of structure files in the structure viewer and mapped to
1918    * Jalview. This may be zero if the files are still in the process of loading
1919    * in the viewer.
1920    *
1921    * @return
1922    */
 
1923  238 toggle public int getMappedStructureCount()
1924    {
1925  238 String[] files = getStructureFiles();
1926  238 return files == null ? 0 : files.length;
1927    }
1928   
1929    /**
1930    * Starts a thread that waits for the external viewer program process to
1931    * finish, so that we can then close the associated resources. This avoids
1932    * leaving orphaned viewer panels in Jalview if the user closes the external
1933    * viewer.
1934    *
1935    * @param p
1936    */
 
1937  0 toggle protected void startExternalViewerMonitor(Process p)
1938    {
1939  0 externalViewerMonitor = new Thread(new Runnable()
1940    {
1941   
 
1942  0 toggle @Override
1943    public void run()
1944    {
1945  0 try
1946    {
1947  0 p.waitFor();
1948  0 JalviewStructureDisplayI display = getViewer();
1949  0 if (display != null)
1950    {
1951  0 display.closeViewer(false);
1952    }
1953    } catch (InterruptedException e)
1954    {
1955    // exit thread if Chimera Viewer is closed in Jalview
1956    }
1957    }
1958    });
1959  0 externalViewerMonitor.start();
1960    }
1961   
1962    /**
1963    * If supported by the external structure viewer, sends it commands to notify
1964    * model or selection changes to the specified URL (where Jalview has started
1965    * a listener)
1966    *
1967    * @param uri
1968    */
 
1969  0 toggle protected void startListening(String uri)
1970    {
1971  0 List<StructureCommandI> commands = getCommandGenerator()
1972    .startNotifications(uri);
1973  0 if (commands != null)
1974    {
1975  0 executeCommands(commands, false, null);
1976    }
1977    }
1978   
1979    /**
1980    * If supported by the external structure viewer, sends it commands to stop
1981    * notifying model or selection changes
1982    */
 
1983  40 toggle protected void stopListening()
1984    {
1985  40 List<StructureCommandI> commands = getCommandGenerator()
1986    .stopNotifications();
1987  40 if (commands != null)
1988    {
1989  0 executeCommands(commands, false, null);
1990    }
1991    }
1992   
1993    /**
1994    * If supported by the structure viewer, queries it for all residue attributes
1995    * with the given attribute name, and creates features on corresponding
1996    * residues of the alignment. Returns the number of features added.
1997    *
1998    * @param attName
1999    * @param alignmentPanel
2000    * @return
2001    */
 
2002  0 toggle public int copyStructureAttributesToFeatures(String attName,
2003    AlignmentPanel alignmentPanel)
2004    {
2005  0 StructureCommandI cmd = getCommandGenerator()
2006    .getResidueAttributes(attName);
2007  0 if (cmd == null)
2008    {
2009  0 return 0;
2010    }
2011  0 List<String> residueAttributes = executeCommand(cmd, true);
2012   
2013  0 int featuresAdded = createFeaturesForAttributes(attName,
2014    residueAttributes);
2015  0 if (featuresAdded > 0)
2016    {
2017  0 alignmentPanel.getFeatureRenderer().featuresAdded();
2018    }
2019  0 return featuresAdded;
2020    }
2021   
2022    /**
2023    * Parses {@code residueAttributes} and creates sequence features on any
2024    * mapped alignment residues. Returns the number of features created.
2025    * <p>
2026    * {@code residueAttributes} is the reply from the structure viewer to a
2027    * command to list any residue attributes for the given attribute name. Syntax
2028    * and parsing of this is viewer-specific.
2029    *
2030    * @param attName
2031    * @param residueAttributes
2032    * @return
2033    */
 
2034  0 toggle protected int createFeaturesForAttributes(String attName,
2035    List<String> residueAttributes)
2036    {
2037  0 return 0;
2038    }
2039   
2040    /**
2041    * list the ligands available for display/hiding in the current view
2042    *
2043    * @return HETATM CODE:Molecule name
2044    */
 
2045  0 toggle public Map<String, String> getHetatmNames()
2046    {
2047  0 return Collections.EMPTY_MAP;
2048    }
2049   
2050    /**
2051    * Generates and executes a command to show the given hetatm types as CPK
2052    *
2053    * @param toShow
2054    * - one or more of strings from getHetatmNames
2055    */
 
2056  0 toggle public void showHetatms(List<String> toShow)
2057    {
2058  0 executeCommands(commandGenerator.showHetatms(toShow), false,
2059    "Adjusting hetatm visibility");
2060    }
2061   
2062    }