/*
* Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
* Copyright (C) $$Year-Rel$$ The Jalview Authors
*
* This file is part of Jalview.
*
* Jalview is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* Jalview is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Jalview. If not, see .
* The Jalview Authors are detailed in the 'AUTHORS' file.
*/
package jalview.gui;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import jalview.api.structures.JalviewStructureDisplayI;
import jalview.bin.Cache;
import jalview.bin.Console;
import jalview.datamodel.PDBEntry;
import jalview.datamodel.SequenceI;
import jalview.datamodel.StructureViewerModel;
import jalview.structure.StructureMapping;
import jalview.structure.StructureSelectionManager;
import jalview.util.MessageManager;
import jalview.util.Platform;
import jalview.ws.DBRefFetcher;
import jalview.ws.seqfetcher.DbSourceProxy;
import jalview.ws.sifts.SiftsSettings;
/**
* A proxy for handling structure viewers, that orchestrates adding selected
* structures, associated with sequences in Jalview, to an existing viewer, or
* opening a new one. Currently supports either Jmol or Chimera as the structure
* viewer.
*
* @author jprocter
*/
public class StructureViewer
{
static
{
Platform.loadStaticResource("core/core_jvjmol.z.js",
"org.jmol.viewer.Viewer");
}
private static final String UNKNOWN_VIEWER_TYPE = "Unknown structure viewer type ";
StructureSelectionManager ssm;
/**
* decide if new structures are aligned to existing ones
*/
private boolean superposeAdded = true;
public enum ViewerType
{
JMOL, CHIMERA, CHIMERAX, PYMOL
};
/**
* Constructor
*
* @param structureSelectionManager
*/
public StructureViewer(
StructureSelectionManager structureSelectionManager)
{
ssm = structureSelectionManager;
}
/**
* Factory to create a proxy for modifying existing structure viewer
*
*/
public static StructureViewer reconfigure(
JalviewStructureDisplayI display)
{
StructureViewer sv = new StructureViewer(display.getBinding().getSsm());
sv.sview = display;
return sv;
}
@Override
public String toString()
{
if (sview != null)
{
return sview.toString();
}
return "New View";
}
/**
*
* @return ViewerType for currently configured structure viewer
*/
public static ViewerType getViewerType()
{
String viewType = Cache.getDefault(Preferences.STRUCTURE_DISPLAY,
ViewerType.JMOL.name());
return ViewerType.valueOf(viewType);
}
public void setViewerType(ViewerType type)
{
Cache.setProperty(Preferences.STRUCTURE_DISPLAY, type.name());
}
/**
* View multiple PDB entries, each with associated sequences
*
* @param pdbs
* @param seqs
* @param ap
* @return
*/
public JalviewStructureDisplayI viewStructures(PDBEntry[] pdbs,
SequenceI[] seqs, AlignmentPanel ap)
{
JalviewStructureDisplayI viewer = onlyOnePdb(pdbs, seqs, ap);
if (viewer != null)
{
/*
* user added structure to an existing viewer - all done
*/
return viewer;
}
ViewerType viewerType = getViewerType();
Map seqsForPdbs = getSequencesForPdbs(pdbs,
seqs);
PDBEntry[] pdbsForFile = seqsForPdbs.keySet()
.toArray(new PDBEntry[seqsForPdbs.size()]);
SequenceI[][] theSeqs = seqsForPdbs.values()
.toArray(new SequenceI[seqsForPdbs.size()][]);
if (sview != null)
{
sview.setAlignAddedStructures(superposeAdded);
new Thread(new Runnable()
{
@Override
public void run()
{
for (int pdbep = 0; pdbep < pdbsForFile.length; pdbep++)
{
PDBEntry pdb = pdbsForFile[pdbep];
if (!sview.addAlreadyLoadedFile(theSeqs[pdbep], null, ap,
pdb.getId()))
{
sview.addToExistingViewer(pdb, theSeqs[pdbep], null, ap,
pdb.getId());
}
}
sview.updateTitleAndMenus();
}
}).start();
return sview;
}
if (viewerType.equals(ViewerType.JMOL))
{
sview = new AppJmol(ap, superposeAdded, pdbsForFile, theSeqs);
}
else if (viewerType.equals(ViewerType.CHIMERA))
{
sview = new ChimeraViewFrame(pdbsForFile, superposeAdded, theSeqs,
ap);
}
else if (viewerType.equals(ViewerType.CHIMERAX))
{
sview = new ChimeraXViewFrame(pdbsForFile, superposeAdded, theSeqs,
ap);
}
else if (viewerType.equals(ViewerType.PYMOL))
{
sview = new PymolViewer(pdbsForFile, superposeAdded, theSeqs, ap);
}
else
{
Console.error(UNKNOWN_VIEWER_TYPE + getViewerType().toString());
}
return sview;
}
/**
* Converts the list of selected PDB entries (possibly including duplicates
* for multiple chains), and corresponding sequences, into a map of sequences
* for each distinct PDB file. Returns null if either argument is null, or
* their lengths do not match.
*
* @param pdbs
* @param seqs
* @return
*/
Map getSequencesForPdbs(PDBEntry[] pdbs,
SequenceI[] seqs)
{
if (pdbs == null || seqs == null || pdbs.length != seqs.length)
{
return null;
}
/*
* we want only one 'representative' PDBEntry per distinct file name
* (there may be entries for distinct chains)
*/
Map pdbsSeen = new HashMap<>();
/*
* LinkedHashMap preserves order of PDB entries (significant if they
* will get superimposed to the first structure)
*/
Map> pdbSeqs = new LinkedHashMap<>();
for (int i = 0; i < pdbs.length; i++)
{
PDBEntry pdb = pdbs[i];
SequenceI seq = seqs[i];
String pdbFile = pdb.getFile();
if (pdbFile == null || pdbFile.length() == 0)
{
pdbFile = pdb.getId();
}
if (!pdbsSeen.containsKey(pdbFile))
{
pdbsSeen.put(pdbFile, pdb);
pdbSeqs.put(pdb, new ArrayList());
}
else
{
pdb = pdbsSeen.get(pdbFile);
}
List seqsForPdb = pdbSeqs.get(pdb);
if (!seqsForPdb.contains(seq))
{
seqsForPdb.add(seq);
}
}
/*
* convert to Map
*/
Map result = new LinkedHashMap<>();
for (Entry> entry : pdbSeqs.entrySet())
{
List theSeqs = entry.getValue();
result.put(entry.getKey(),
theSeqs.toArray(new SequenceI[theSeqs.size()]));
}
return result;
}
/**
* A strictly temporary method pending JAL-1761 refactoring. Determines if all
* the passed PDB entries are the same (this is the case if selected sequences
* to view structure for are chains of the same structure). If so, calls the
* single-pdb version of viewStructures and returns the viewer, else returns
* null.
*
* @param pdbs
* @param seqsForPdbs
* @param ap
* @return
*/
private JalviewStructureDisplayI onlyOnePdb(PDBEntry[] pdbs,
SequenceI[] seqsForPdbs, AlignmentPanel ap)
{
List seqs = new ArrayList<>();
if (pdbs == null || pdbs.length == 0)
{
return null;
}
int i = 0;
String firstFile = pdbs[0].getFile();
for (PDBEntry pdb : pdbs)
{
String pdbFile = pdb.getFile();
if (pdbFile == null || !pdbFile.equals(firstFile))
{
return null;
}
SequenceI pdbseq = seqsForPdbs[i++];
if (pdbseq != null)
{
seqs.add(pdbseq);
}
}
return viewStructures(pdbs[0], seqs.toArray(new SequenceI[seqs.size()]),
ap);
}
JalviewStructureDisplayI sview = null;
public JalviewStructureDisplayI viewStructures(PDBEntry pdb,
SequenceI[] seqsForPdb, AlignmentPanel ap)
{
if (sview != null)
{
sview.setAlignAddedStructures(superposeAdded);
String pdbId = pdb.getId();
if (!sview.addAlreadyLoadedFile(seqsForPdb, null, ap, pdbId))
{
sview.addToExistingViewer(pdb, seqsForPdb, null, ap, pdbId);
}
sview.updateTitleAndMenus();
sview.raiseViewer();
return sview;
}
ViewerType viewerType = getViewerType();
if (viewerType.equals(ViewerType.JMOL))
{
sview = new AppJmol(pdb, seqsForPdb, null, ap);
}
else if (viewerType.equals(ViewerType.CHIMERA))
{
sview = new ChimeraViewFrame(pdb, seqsForPdb, null, ap);
}
else if (viewerType.equals(ViewerType.CHIMERAX))
{
sview = new ChimeraXViewFrame(pdb, seqsForPdb, null, ap);
}
else if (viewerType.equals(ViewerType.PYMOL))
{
sview = new PymolViewer(pdb, seqsForPdb, null, ap);
}
else
{
Console.error(UNKNOWN_VIEWER_TYPE + getViewerType().toString());
}
return sview;
}
/**
* Creates a new panel controlling a structure viewer
*
* @param type
* @param pdbf
* @param id
* @param sq
* @param alignPanel
* @param viewerData
* @param sessionFile
* @param vid
* @return
*/
public static JalviewStructureDisplayI createView(ViewerType type,
AlignmentPanel alignPanel, StructureViewerModel viewerData,
String sessionFile, String vid)
{
JalviewStructureDisplayI viewer = null;
switch (type)
{
case JMOL:
viewer = new AppJmol(viewerData, alignPanel, sessionFile, vid);
// todo or construct and then openSession(sessionFile)?
break;
case CHIMERA:
viewer = new ChimeraViewFrame(viewerData, alignPanel, sessionFile,
vid);
break;
case CHIMERAX:
viewer = new ChimeraXViewFrame(viewerData, alignPanel, sessionFile,
vid);
break;
case PYMOL:
viewer = new PymolViewer(viewerData, alignPanel, sessionFile, vid);
break;
default:
Console.error(UNKNOWN_VIEWER_TYPE + type.toString());
}
return viewer;
}
public boolean isBusy()
{
if (sview != null)
{
if (!sview.hasMapping())
{
return true;
}
}
return false;
}
/**
*
* @param pDBid
* @return true if view is already showing PDBid
*/
public boolean hasPdbId(String pDBid)
{
if (sview == null)
{
return false;
}
return sview.getBinding().hasPdbId(pDBid);
}
public boolean isVisible()
{
return sview != null && sview.isVisible();
}
public void setSuperpose(boolean alignAddedStructures)
{
superposeAdded = alignAddedStructures;
}
/**
* Launch a minimal implementation of a StructureViewer.
*
* @param alignPanel
* @param pdb
* @param seqs
* @return
*/
public static StructureViewer launchStructureViewer(
AlignmentPanel alignPanel, PDBEntry pdb, SequenceI[] seqs)
{
return launchStructureViewer(alignPanel, new PDBEntry[] { pdb }, seqs,
false, null, null);
}
/**
* Adds PDB structures to a new or existing structure viewer
*
* @param ssm
* @param pdbEntriesToView
* @param alignPanel
* @param sequences
* @return
*/
protected static StructureViewer launchStructureViewer(
final AlignmentPanel ap, final PDBEntry[] pdbEntriesToView,
SequenceI[] sequences, boolean superimpose,
StructureViewer theViewer, IProgressIndicator pb)
{
final StructureSelectionManager ssm = ap.getStructureSelectionManager();
if (theViewer == null)
theViewer = new StructureViewer(ssm);
long progressId = sequences.hashCode();
if (pb != null)
pb.setProgressBar(MessageManager.getString(
"status.launching_3d_structure_viewer"), progressId);
theViewer.setSuperpose(superimpose);
/*
* remember user's choice of superimpose or not
*/
Cache.setProperty(StructureChooser.AUTOSUPERIMPOSE,
Boolean.valueOf(superimpose).toString());
if (pb != null)
pb.setProgressBar(null, progressId);
if (SiftsSettings.isMapWithSifts())
{
List seqsWithoutSourceDBRef = new ArrayList<>();
int p = 0;
// TODO: skip PDBEntry:Sequence pairs where PDBEntry doesn't look like a
// real PDB ID. For moment, we can also safely do this if there is already
// a known mapping between the PDBEntry and the sequence.
for (SequenceI seq : sequences)
{
PDBEntry pdbe = pdbEntriesToView[p++];
if (pdbe != null && pdbe.getFile() != null)
{
StructureMapping[] smm = ssm.getMapping(pdbe.getFile());
if (smm != null && smm.length > 0)
{
for (StructureMapping sm : smm)
{
if (sm.getSequence() == seq)
{
continue;
}
}
}
}
if (seq.getPrimaryDBRefs().isEmpty())
{
seqsWithoutSourceDBRef.add(seq);
continue;
}
}
if (!seqsWithoutSourceDBRef.isEmpty())
{
int y = seqsWithoutSourceDBRef.size();
if (pb != null)
pb.setProgressBar(MessageManager.formatMessage(
"status.fetching_dbrefs_for_sequences_without_valid_refs",
y), progressId);
SequenceI[] seqWithoutSrcDBRef = seqsWithoutSourceDBRef
.toArray(new SequenceI[y]);
DBRefFetcher dbRefFetcher = new DBRefFetcher(seqWithoutSrcDBRef);
dbRefFetcher.fetchDBRefs(true);
if (pb != null)
pb.setProgressBar("Fetch complete.", progressId); // todo i18n
}
}
if (pdbEntriesToView.length > 1)
{
if (pb != null)
pb.setProgressBar(MessageManager.getString(
"status.fetching_3d_structures_for_selected_entries"),
progressId);
theViewer.viewStructures(pdbEntriesToView, sequences, ap);
}
else
{
if (pb != null)
pb.setProgressBar(MessageManager.formatMessage(
"status.fetching_3d_structures_for",
pdbEntriesToView[0].getId()), progressId);
theViewer.viewStructures(pdbEntriesToView[0], sequences, ap);
}
if (pb != null)
pb.setProgressBar(null, progressId);
// remember the last viewer we used...
StructureChooser.lastTargetedView = theViewer;
return theViewer;
}
}