JAL-3253 temporary branch SwingJS upgrade with testNG fixes Java 8
[jalview.git] / src / jalview / gui / StructureViewer.java
index ccb3c18..17b786d 100644 (file)
@@ -1,6 +1,6 @@
 /*
- * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
- * Copyright (C) 2014 The Jalview Authors
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
  * 
  * This file is part of Jalview.
  * 
  */
 package jalview.gui;
 
-import java.awt.Rectangle;
-
 import jalview.api.structures.JalviewStructureDisplayI;
 import jalview.bin.Cache;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
-import jalview.gui.StructureViewer.Viewer;
+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.sifts.SiftsSettings;
+
+import java.awt.Rectangle;
+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;
 
 /**
- * proxy for handling structure viewers.
- * 
- * this allows new views to be created with the currently configured viewer, the
- * preferred viewer to be set/read and existing views created previously with a
- * particular viewer to be recovered
+ * 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.ensureJmol();
+  }
+
+  private static final String UNKNOWN_VIEWER_TYPE = "Unknown structure viewer type ";
+
   StructureSelectionManager ssm;
 
-  public enum Viewer
+  /**
+   * decide if new structures are aligned to existing ones
+   */
+  private boolean superposeAdded = true;
+
+  public enum ViewerType
   {
     JMOL, CHIMERA
-  };
+  }
 
-  public Viewer getViewerType()
+  /**
+   * Constructor
+   * 
+   * @param structureSelectionManager
+   */
+  public StructureViewer(StructureSelectionManager structureSelectionManager)
   {
-    String viewType = Cache.getDefault("STRUCTURE_DISPLAY", "JMOL");
-    return Viewer.valueOf(viewType);
+    ssm = structureSelectionManager;
   }
 
-  public void setViewerType(Viewer type)
+  /**
+   * Factory to create a proxy for modifying existing structure viewer
+   * 
+   */
+  public static StructureViewer reconfigure(
+          JalviewStructureDisplayI display)
   {
-    Cache.setProperty("STRUCTURE_DISPLAY", type.toString());
+    StructureViewer sv = new StructureViewer(display.getBinding().getSsm());
+    sv.sview = display;
+    return sv;
   }
 
-  public StructureViewer(StructureSelectionManager structureSelectionManager)
+  @Override
+  public String toString()
   {
-    ssm = structureSelectionManager;
+    if (sview != null)
+    {
+      return sview.toString();
+    }
+    return "New View";
+  }
+  public ViewerType getViewerType()
+  {
+    String viewType = Cache.getDefault(Preferences.STRUCTURE_DISPLAY,
+            ViewerType.JMOL.name());
+    return ViewerType.valueOf(viewType);
   }
 
-  public JalviewStructureDisplayI viewStructures(AlignmentPanel ap,
-          PDBEntry[] pr, SequenceI[][] collateForPDB)
+  public void setViewerType(ViewerType type)
   {
-    return viewStructures(getViewerType(), ap, pr, collateForPDB);
+    Cache.setProperty(Preferences.STRUCTURE_DISPLAY, type.name());
   }
 
-  public JalviewStructureDisplayI viewStructures(Viewer viewerType,
-          AlignmentPanel ap, PDBEntry[] pr, SequenceI[][] collateForPDB)
+  /**
+   * 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 sview = null;
-    if (viewerType.equals(Viewer.JMOL))
+    JalviewStructureDisplayI viewer = onlyOnePdb(pdbs, seqs, ap);
+    if (viewer != null)
     {
-      sview = new AppJmol(ap, pr, ap.av.collateForPDB(pr));
+      /*
+       * user added structure to an existing viewer - all done
+       */
+      return viewer;
     }
-    else if (viewerType.equals(Viewer.CHIMERA))
+
+    ViewerType viewerType = getViewerType();
+
+    Map<PDBEntry, SequenceI[]> 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 = new ChimeraViewFrame(ap, pr, ap.av.collateForPDB(pr));
+      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
     {
-      Cache.log.error("Unknown structure viewer type "
-              + getViewerType().toString());
+      Cache.log.error(UNKNOWN_VIEWER_TYPE + getViewerType().toString());
     }
     return sview;
   }
 
-  public JalviewStructureDisplayI viewStructures(Viewer viewerType,
-          AlignmentPanel ap, PDBEntry pr, SequenceI[] collateForPDB)
+  /**
+   * 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<PDBEntry, SequenceI[]> getSequencesForPdbs(PDBEntry[] pdbs,
+          SequenceI[] seqs)
   {
-    JalviewStructureDisplayI sview = null;
-    if (viewerType.equals(Viewer.JMOL))
+    if (pdbs == null || seqs == null || pdbs.length != seqs.length)
     {
-      sview = new AppJmol(pr, collateForPDB, null, ap);
+      return null;
     }
-    else if (viewerType.equals(Viewer.CHIMERA))
+
+    /*
+     * we want only one 'representative' PDBEntry per distinct file name
+     * (there may be entries for distinct chains)
+     */
+    Map<String, PDBEntry> pdbsSeen = new HashMap<>();
+
+    /*
+     * LinkedHashMap preserves order of PDB entries (significant if they
+     * will get superimposed to the first structure)
+     */
+    Map<PDBEntry, List<SequenceI>> pdbSeqs = new LinkedHashMap<>();
+    for (int i = 0; i < pdbs.length; i++)
     {
-      sview = new ChimeraViewFrame(pr, collateForPDB, null, ap);
+      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<SequenceI>());
+      }
+      else
+      {
+        pdb = pdbsSeen.get(pdbFile);
+      }
+      List<SequenceI> seqsForPdb = pdbSeqs.get(pdb);
+      if (!seqsForPdb.contains(seq))
+      {
+        seqsForPdb.add(seq);
+      }
     }
-    else
+
+    /*
+     * convert to Map<PDBEntry, SequenceI[]>
+     */
+    Map<PDBEntry, SequenceI[]> result = new LinkedHashMap<>();
+    for (Entry<PDBEntry, List<SequenceI>> entry : pdbSeqs.entrySet())
     {
-      Cache.log.error("Unknown structure viewer type "
-              + getViewerType().toString());
+      List<SequenceI> theSeqs = entry.getValue();
+      result.put(entry.getKey(),
+              theSeqs.toArray(new SequenceI[theSeqs.size()]));
     }
-    return sview;
+
+    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<SequenceI> 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[] sequenceIs, Object object, AlignmentPanel ap)
+          SequenceI[] seqsForPdb, AlignmentPanel ap)
   {
-    return viewStructures(getViewerType(), ap, pdb, sequenceIs);
+    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
+    {
+      Cache.log.error(UNKNOWN_VIEWER_TYPE + getViewerType().toString());
+    }
+    return sview;
   }
 
-  public JalviewStructureDisplayI createView(Viewer jmol, String[] pdbf,
+  /**
+   * Create a new panel controlling a structure viewer.
+   * 
+   * @param type
+   * @param pdbf
+   * @param id
+   * @param sq
+   * @param alignPanel
+   * @param viewerData
+   * @param fileloc
+   * @param rect
+   * @param vid
+   * @return
+   */
+  public JalviewStructureDisplayI createView(ViewerType type, String[] pdbf,
           String[] id, SequenceI[][] sq, AlignmentPanel alignPanel,
-          boolean useinJmolsuperpos, boolean usetoColourbyseq,
-          boolean jmolColouring, String fileloc, Rectangle rect, String vid)
+          StructureViewerModel viewerData, String fileloc, Rectangle rect,
+          String vid)
   {
-    JalviewStructureDisplayI sview = null;
-    switch (getViewerType())
+    final boolean useinViewerSuperpos = viewerData.isAlignWithPanel();
+    final boolean usetoColourbyseq = viewerData.isColourWithAlignPanel();
+    final boolean viewerColouring = viewerData.isColourByViewer();
+
+    switch (type)
     {
     case JMOL:
-
-      sview = new AppJmol(pdbf, id, sq, alignPanel, useinJmolsuperpos,
-              usetoColourbyseq, jmolColouring, fileloc, rect, vid);
-
+      sview = new AppJmol(pdbf, id, sq, alignPanel, usetoColourbyseq,
+              useinViewerSuperpos, viewerColouring, fileloc, rect, vid);
       break;
     case CHIMERA:
+      Cache.log.error(
+              "Unsupported structure viewer type " + type.toString());
       break;
     default:
-      Cache.log.error("Unknown structure viewer type "
-              + getViewerType().toString());
+      Cache.log.error(UNKNOWN_VIEWER_TYPE + type.toString());
     }
     return sview;
   }
 
+  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);
+  }
+
+  /**
+   * Launch a structure viewer with or without an open StructureChooser.
+   * 
+   * Moved from StructureChooser to enable JalviewJS startup with structure
+   * display.
+   * 
+   * @param ap
+   * @param pdbEntriesToView
+   * @param sequences
+   * @param superimpose
+   * @param theViewer
+   * @param pb
+   * @return
+   */
+  protected static StructureViewer launchStructureViewer(
+          final AlignmentPanel ap,
+          final PDBEntry[] pdbEntriesToView, SequenceI[] sequences,
+          boolean superimpose, StructureViewer theViewer,
+          IProgressIndicator pb)
+  {
+    final StructureSelectionManager ssm = ap.getStructureSelectionManager();
+    long progressId = sequences.hashCode();
+    if (pb != null)
+    {
+      pb.setProgressBar(MessageManager
+            .getString("status.launching_3d_structure_viewer"), progressId);
+    }
+    if (theViewer == null)
+    {
+      theViewer = new StructureViewer(ssm);
+    }
+    theViewer.setSuperpose(superimpose);
+  
+    if (pb != null)
+    {
+      pb.setProgressBar(null, progressId);
+    }
+    if (SiftsSettings.isMapWithSifts())
+    {
+      List<SequenceI> 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...
+    Desktop.getInstance().lastTargetedView = theViewer;
+    return theViewer;
+  }
+
 }