Clover icon

Coverage Report

  1. Project Clover database Thu Dec 4 2025 16:11:35 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,054
1,274
257
0.49
6.43
40.5
3.17

Classes

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

Contributing tests

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