/*
* 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.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
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.StructureSelectionManager;
/**
* 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
{
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;
/**
* whether to open structures in their own thread or not
*/
private boolean async = true;
public void setAsync(boolean b)
{
async = b;
}
public enum ViewerType
{
JMOL, CHIMERA, CHIMERAX, PYMOL;
public static ViewerType getFromString(String viewerString)
{
ViewerType viewerType = null;
if (!"none".equals(viewerString))
{
for (ViewerType v : EnumSet.allOf(ViewerType.class))
{
String name = v.name().toLowerCase(Locale.ROOT).replaceAll(" ",
"");
if (viewerString.equals(name))
{
viewerType = v;
break;
}
}
}
return viewerType;
}
};
/**
* 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)
{
return viewStructures(pdbs, seqs, ap, null);
}
public JalviewStructureDisplayI viewStructures(PDBEntry[] pdbs,
SequenceI[] seqs, AlignmentPanel ap, ViewerType viewerType)
{
JalviewStructureDisplayI viewer = onlyOnePdb(pdbs, seqs, ap);
if (viewer != null)
{
/*
* user added structure to an existing viewer - all done
*/
return viewer;
}
if (viewerType == null)
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);
Runnable viewRunnable = 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();
}
};
if (async)
{
new Thread(viewRunnable).start();
}
else
{
viewRunnable.run();
}
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 getJalviewStructureDisplay()
{
return sview;
}
public JalviewStructureDisplayI viewStructures(PDBEntry pdb,
SequenceI[] seqsForPdb, AlignmentPanel ap)
{
return viewStructures(pdb, seqsForPdb, ap, null);
}
public JalviewStructureDisplayI viewStructures(PDBEntry pdb,
SequenceI[] seqsForPdb, AlignmentPanel ap, ViewerType viewerType)
{
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;
}
if (viewerType == null)
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 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;
}
}