2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
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.
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.
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.
23 import java.util.ArrayList;
24 import java.util.HashMap;
25 import java.util.LinkedHashMap;
26 import java.util.List;
28 import java.util.Map.Entry;
29 import jalview.api.structures.JalviewStructureDisplayI;
30 import jalview.bin.Cache;
31 import jalview.bin.Console;
32 import jalview.datamodel.PDBEntry;
33 import jalview.datamodel.SequenceI;
34 import jalview.datamodel.StructureViewerModel;
35 import jalview.structure.StructureMapping;
36 import jalview.structure.StructureSelectionManager;
37 import jalview.util.MessageManager;
38 import jalview.util.Platform;
39 import jalview.ws.DBRefFetcher;
40 import jalview.ws.seqfetcher.DbSourceProxy;
41 import jalview.ws.sifts.SiftsSettings;
46 * A proxy for handling structure viewers, that orchestrates adding selected
47 * structures, associated with sequences in Jalview, to an existing viewer, or
48 * opening a new one. Currently supports either Jmol or Chimera as the structure
53 public class StructureViewer
58 Platform.loadStaticResource("core/core_jvjmol.z.js",
59 "org.jmol.viewer.Viewer");
65 private static final String UNKNOWN_VIEWER_TYPE = "Unknown structure viewer type ";
67 StructureSelectionManager ssm;
70 * decide if new structures are aligned to existing ones
72 private boolean superposeAdded = true;
74 public enum ViewerType
76 JMOL, CHIMERA, CHIMERAX, PYMOL
82 * @param structureSelectionManager
84 public StructureViewer(
85 StructureSelectionManager structureSelectionManager)
87 ssm = structureSelectionManager;
91 * Factory to create a proxy for modifying existing structure viewer
94 public static StructureViewer reconfigure(
95 JalviewStructureDisplayI display)
97 StructureViewer sv = new StructureViewer(display.getBinding().getSsm());
103 public String toString()
107 return sview.toString();
114 * @return ViewerType for currently configured structure viewer
116 public static ViewerType getViewerType()
118 String viewType = Cache.getDefault(Preferences.STRUCTURE_DISPLAY,
119 ViewerType.JMOL.name());
120 return ViewerType.valueOf(viewType);
123 public void setViewerType(ViewerType type)
125 Cache.setProperty(Preferences.STRUCTURE_DISPLAY, type.name());
129 * View multiple PDB entries, each with associated sequences
136 public JalviewStructureDisplayI viewStructures(PDBEntry[] pdbs,
137 SequenceI[] seqs, AlignmentPanel ap)
139 JalviewStructureDisplayI viewer = onlyOnePdb(pdbs, seqs, ap);
143 * user added structure to an existing viewer - all done
148 ViewerType viewerType = getViewerType();
150 Map<PDBEntry, SequenceI[]> seqsForPdbs = getSequencesForPdbs(pdbs,
152 PDBEntry[] pdbsForFile = seqsForPdbs.keySet()
153 .toArray(new PDBEntry[seqsForPdbs.size()]);
154 SequenceI[][] theSeqs = seqsForPdbs.values()
155 .toArray(new SequenceI[seqsForPdbs.size()][]);
158 sview.setAlignAddedStructures(superposeAdded);
159 new Thread(new Runnable()
165 for (int pdbep = 0; pdbep < pdbsForFile.length; pdbep++)
167 PDBEntry pdb = pdbsForFile[pdbep];
168 if (!sview.addAlreadyLoadedFile(theSeqs[pdbep], null, ap,
171 sview.addToExistingViewer(pdb, theSeqs[pdbep], null, ap,
176 sview.updateTitleAndMenus();
182 if (viewerType.equals(ViewerType.JMOL))
184 sview = new AppJmol(ap, superposeAdded, pdbsForFile, theSeqs);
186 else if (viewerType.equals(ViewerType.CHIMERA))
188 sview = new ChimeraViewFrame(pdbsForFile, superposeAdded, theSeqs,
191 else if (viewerType.equals(ViewerType.CHIMERAX))
193 sview = new ChimeraXViewFrame(pdbsForFile, superposeAdded, theSeqs,
196 else if (viewerType.equals(ViewerType.PYMOL))
198 sview = new PymolViewer(pdbsForFile, superposeAdded, theSeqs, ap);
202 Console.error(UNKNOWN_VIEWER_TYPE + getViewerType().toString());
208 * Converts the list of selected PDB entries (possibly including duplicates
209 * for multiple chains), and corresponding sequences, into a map of sequences
210 * for each distinct PDB file. Returns null if either argument is null, or
211 * their lengths do not match.
217 Map<PDBEntry, SequenceI[]> getSequencesForPdbs(PDBEntry[] pdbs,
220 if (pdbs == null || seqs == null || pdbs.length != seqs.length)
226 * we want only one 'representative' PDBEntry per distinct file name
227 * (there may be entries for distinct chains)
229 Map<String, PDBEntry> pdbsSeen = new HashMap<>();
232 * LinkedHashMap preserves order of PDB entries (significant if they
233 * will get superimposed to the first structure)
235 Map<PDBEntry, List<SequenceI>> pdbSeqs = new LinkedHashMap<>();
236 for (int i = 0; i < pdbs.length; i++)
238 PDBEntry pdb = pdbs[i];
239 SequenceI seq = seqs[i];
240 String pdbFile = pdb.getFile();
241 if (pdbFile == null || pdbFile.length() == 0)
243 pdbFile = pdb.getId();
245 if (!pdbsSeen.containsKey(pdbFile))
247 pdbsSeen.put(pdbFile, pdb);
248 pdbSeqs.put(pdb, new ArrayList<SequenceI>());
252 pdb = pdbsSeen.get(pdbFile);
254 List<SequenceI> seqsForPdb = pdbSeqs.get(pdb);
255 if (!seqsForPdb.contains(seq))
262 * convert to Map<PDBEntry, SequenceI[]>
264 Map<PDBEntry, SequenceI[]> result = new LinkedHashMap<>();
265 for (Entry<PDBEntry, List<SequenceI>> entry : pdbSeqs.entrySet())
267 List<SequenceI> theSeqs = entry.getValue();
268 result.put(entry.getKey(),
269 theSeqs.toArray(new SequenceI[theSeqs.size()]));
276 * A strictly temporary method pending JAL-1761 refactoring. Determines if all
277 * the passed PDB entries are the same (this is the case if selected sequences
278 * to view structure for are chains of the same structure). If so, calls the
279 * single-pdb version of viewStructures and returns the viewer, else returns
287 private JalviewStructureDisplayI onlyOnePdb(PDBEntry[] pdbs,
288 SequenceI[] seqsForPdbs, AlignmentPanel ap)
290 List<SequenceI> seqs = new ArrayList<>();
291 if (pdbs == null || pdbs.length == 0)
296 String firstFile = pdbs[0].getFile();
297 for (PDBEntry pdb : pdbs)
299 String pdbFile = pdb.getFile();
300 if (pdbFile == null || !pdbFile.equals(firstFile))
304 SequenceI pdbseq = seqsForPdbs[i++];
310 return viewStructures(pdbs[0], seqs.toArray(new SequenceI[seqs.size()]),
314 JalviewStructureDisplayI sview = null;
316 public JalviewStructureDisplayI viewStructures(PDBEntry pdb,
317 SequenceI[] seqsForPdb, AlignmentPanel ap)
321 sview.setAlignAddedStructures(superposeAdded);
322 String pdbId = pdb.getId();
323 if (!sview.addAlreadyLoadedFile(seqsForPdb, null, ap, pdbId))
325 sview.addToExistingViewer(pdb, seqsForPdb, null, ap, pdbId);
327 sview.updateTitleAndMenus();
331 ViewerType viewerType = getViewerType();
332 if (viewerType.equals(ViewerType.JMOL))
334 sview = new AppJmol(pdb, seqsForPdb, null, ap);
336 else if (viewerType.equals(ViewerType.CHIMERA))
338 sview = new ChimeraViewFrame(pdb, seqsForPdb, null, ap);
340 else if (viewerType.equals(ViewerType.CHIMERAX))
342 sview = new ChimeraXViewFrame(pdb, seqsForPdb, null, ap);
344 else if (viewerType.equals(ViewerType.PYMOL))
346 sview = new PymolViewer(pdb, seqsForPdb, null, ap);
350 Console.error(UNKNOWN_VIEWER_TYPE + getViewerType().toString());
356 * Creates a new panel controlling a structure viewer
368 public static JalviewStructureDisplayI createView(ViewerType type,
369 AlignmentPanel alignPanel, StructureViewerModel viewerData,
370 String sessionFile, String vid)
372 JalviewStructureDisplayI viewer = null;
377 viewer = new AppJmol(viewerData, alignPanel, sessionFile, vid);
378 // todo or construct and then openSession(sessionFile)?
381 viewer = new ChimeraViewFrame(viewerData, alignPanel, sessionFile,
385 viewer = new ChimeraXViewFrame(viewerData, alignPanel, sessionFile,
389 viewer = new PymolViewer(viewerData, alignPanel, sessionFile, vid);
392 Console.error(UNKNOWN_VIEWER_TYPE + type.toString());
397 public boolean isBusy()
401 if (!sview.hasMapping())
412 * @return true if view is already showing PDBid
414 public boolean hasPdbId(String pDBid)
421 return sview.getBinding().hasPdbId(pDBid);
424 public boolean isVisible()
426 return sview != null && sview.isVisible();
429 public void setSuperpose(boolean alignAddedStructures)
431 superposeAdded = alignAddedStructures;
435 * Launch a minimal implementation of a StructureViewer.
442 public static StructureViewer launchStructureViewer(
443 AlignmentPanel alignPanel, PDBEntry pdb, SequenceI[] seqs)
445 return launchStructureViewer(alignPanel, new PDBEntry[] { pdb }, seqs,
450 * Adds PDB structures to a new or existing structure viewer
453 * @param pdbEntriesToView
458 protected static StructureViewer launchStructureViewer(
459 final AlignmentPanel ap, final PDBEntry[] pdbEntriesToView,
460 SequenceI[] sequences, boolean superimpose,
461 StructureViewer theViewer, IProgressIndicator pb)
463 final StructureSelectionManager ssm = ap.getStructureSelectionManager();
464 if (theViewer == null)
465 theViewer = new StructureViewer(ssm);
466 long progressId = sequences.hashCode();
468 pb.setProgressBar(MessageManager.getString(
469 "status.launching_3d_structure_viewer"), progressId);
470 theViewer.setSuperpose(superimpose);
473 * remember user's choice of superimpose or not
475 Cache.setProperty(StructureChooser.AUTOSUPERIMPOSE,
476 Boolean.valueOf(superimpose).toString());
479 pb.setProgressBar(null, progressId);
480 if (SiftsSettings.isMapWithSifts())
482 List<SequenceI> seqsWithoutSourceDBRef = new ArrayList<>();
484 // TODO: skip PDBEntry:Sequence pairs where PDBEntry doesn't look like a
485 // real PDB ID. For moment, we can also safely do this if there is already
486 // a known mapping between the PDBEntry and the sequence.
487 for (SequenceI seq : sequences)
489 PDBEntry pdbe = pdbEntriesToView[p++];
490 if (pdbe != null && pdbe.getFile() != null)
492 StructureMapping[] smm = ssm.getMapping(pdbe.getFile());
493 if (smm != null && smm.length > 0)
495 for (StructureMapping sm : smm)
497 if (sm.getSequence() == seq)
504 if (seq.getPrimaryDBRefs().isEmpty())
506 seqsWithoutSourceDBRef.add(seq);
510 if (!seqsWithoutSourceDBRef.isEmpty())
512 int y = seqsWithoutSourceDBRef.size();
514 pb.setProgressBar(MessageManager.formatMessage(
515 "status.fetching_dbrefs_for_sequences_without_valid_refs",
517 SequenceI[] seqWithoutSrcDBRef = seqsWithoutSourceDBRef
518 .toArray(new SequenceI[y]);
519 DBRefFetcher dbRefFetcher = new DBRefFetcher(seqWithoutSrcDBRef);
520 dbRefFetcher.fetchDBRefs(true);
523 pb.setProgressBar("Fetch complete.", progressId); // todo i18n
526 if (pdbEntriesToView.length > 1)
529 pb.setProgressBar(MessageManager.getString(
530 "status.fetching_3d_structures_for_selected_entries"),
532 theViewer.viewStructures(pdbEntriesToView, sequences, ap);
537 pb.setProgressBar(MessageManager.formatMessage(
538 "status.fetching_3d_structures_for",
539 pdbEntriesToView[0].getId()), progressId);
540 theViewer.viewStructures(pdbEntriesToView[0], sequences, ap);
543 pb.setProgressBar(null, progressId);
544 // remember the last viewer we used...
545 StructureChooser.lastTargetedView = theViewer;