JAL-3551 working proof of concept of Jalview driving PyMOL
[jalview.git] / src / jalview / gui / PymolViewer.java
diff --git a/src/jalview/gui/PymolViewer.java b/src/jalview/gui/PymolViewer.java
new file mode 100644 (file)
index 0000000..09451be
--- /dev/null
@@ -0,0 +1,338 @@
+package jalview.gui;
+
+import jalview.api.AlignmentViewPanel;
+import jalview.api.FeatureRenderer;
+import jalview.bin.Cache;
+import jalview.datamodel.PDBEntry;
+import jalview.datamodel.SequenceI;
+import jalview.gui.StructureViewer.ViewerType;
+import jalview.io.DataSourceType;
+import jalview.io.StructureFile;
+import jalview.structures.models.AAStructureBindingModel;
+import jalview.util.MessageManager;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.JInternalFrame;
+import javax.swing.event.InternalFrameAdapter;
+import javax.swing.event.InternalFrameEvent;
+
+public class PymolViewer extends StructureViewerBase
+{
+  private static final int myWidth = 500;
+
+  private static final int myHeight = 150;
+
+  private PymolBindingModel binding;
+
+  private String pymolSessionFile;
+
+  public PymolViewer()
+  {
+    super();
+
+    /*
+     * closeViewer will decide whether or not to close this frame
+     * depending on whether user chooses to Cancel or not
+     */
+    setDefaultCloseOperation(JInternalFrame.DO_NOTHING_ON_CLOSE);
+  }
+
+  public PymolViewer(PDBEntry pdb, SequenceI[] seqs, Object object,
+          AlignmentPanel ap)
+  {
+    this();
+    openNewPymol(ap, new PDBEntry[] { pdb },
+            new SequenceI[][]
+            { seqs });
+  }
+
+  public PymolViewer(PDBEntry[] pe, boolean alignAdded, SequenceI[][] seqs,
+          AlignmentPanel ap)
+  {
+    this();
+    setAlignAddedStructures(alignAdded);
+    openNewPymol(ap, pe, seqs);
+  }
+
+  private void openNewPymol(AlignmentPanel ap, PDBEntry[] pe,
+          SequenceI[][] seqs)
+  {
+    createProgressBar();
+    binding = new PymolBindingModel(this, ap.getStructureSelectionManager(),
+            pe, seqs);
+    addAlignmentPanel(ap);
+    useAlignmentPanelForColourbyseq(ap);
+
+    if (pe.length > 1)
+    {
+      useAlignmentPanelForSuperposition(ap);
+    }
+    binding.setColourBySequence(true);
+    setSize(myWidth, myHeight);
+    initMenus();
+
+    addingStructures = false;
+    worker = new Thread(this);
+    worker.start();
+
+    this.addInternalFrameListener(new InternalFrameAdapter()
+    {
+      @Override
+      public void internalFrameClosing(
+              InternalFrameEvent internalFrameEvent)
+      {
+        closeViewer(false);
+      }
+    });
+
+  }
+
+  /**
+   * Create a helper to manage progress bar display
+   */
+  protected void createProgressBar()
+  {
+    if (getProgressIndicator() == null)
+    {
+      setProgressIndicator(new ProgressBar(statusPanel, statusBar));
+    }
+  }
+
+  @Override
+  public void run()
+  {
+    // todo pull up much of this
+  
+    StringBuilder errormsgs = new StringBuilder(128);
+    List<PDBEntry> filePDB = new ArrayList<>();
+    List<Integer> filePDBpos = new ArrayList<>();
+    String[] curfiles = binding.getStructureFiles(); // files currently in viewer
+    for (int pi = 0; pi < binding.getPdbCount(); pi++)
+    {
+      String file = null;
+      PDBEntry thePdbEntry = binding.getPdbEntry(pi);
+      if (thePdbEntry.getFile() == null)
+      {
+        /*
+         * Retrieve PDB data, save to file, attach to PDBEntry
+         */
+        file = fetchPdbFile(thePdbEntry);
+        if (file == null)
+        {
+          errormsgs.append("'" + thePdbEntry.getId() + "' ");
+        }
+      }
+      else
+      {
+        /*
+         * got file already
+         */
+        file = new File(thePdbEntry.getFile()).getAbsoluteFile()
+                .getPath();
+        // todo - skip if already loaded in PyMOL
+      }
+      if (file != null)
+      {
+        filePDB.add(thePdbEntry);
+        filePDBpos.add(Integer.valueOf(pi));
+      }
+    }
+        
+    if (!filePDB.isEmpty())
+    {
+      /*
+       * at least one structure to add to viewer
+       */
+      binding.setFinishedInit(false);
+      if (!addingStructures)
+      {
+        try
+        {
+          initPymol();
+        } catch (Exception ex)
+        {
+          Cache.log.error("Couldn't open PyMOL viewer!", ex);
+        }
+      }
+      int num = -1;
+      for (PDBEntry pe : filePDB)
+      {
+        num++;
+        if (pe.getFile() != null)
+        {
+          try
+          {
+            int pos = filePDBpos.get(num).intValue();
+            long startTime = startProgressBar(getViewerName() + " "
+                    + MessageManager.getString("status.opening_file_for")
+                    + " " + pe.getId());
+            binding.openFile(pe);
+            binding.addSequence(pos, binding.getSequence()[pos]);
+            File fl = new File(pe.getFile());
+            DataSourceType protocol = DataSourceType.URL;
+            try
+            {
+              if (fl.exists())
+              {
+                protocol = DataSourceType.FILE;
+              }
+            } catch (Throwable e)
+            {
+            } finally
+            {
+              stopProgressBar("", startTime);
+            }
+
+            StructureFile pdb = binding.getSsm().setMapping(
+                    binding.getSequence()[pos], binding.getChains()[pos],
+                    pe.getFile(), protocol,
+                    getProgressIndicator());
+            binding.stashFoundChains(pdb, pe.getFile());
+          } catch (Exception ex)
+          {
+            Cache.log.error(
+                    "Couldn't open " + pe.getFile() + " in Chimera viewer!",
+                    ex);
+          } finally
+          {
+            // Cache.log.debug("File locations are " + files);
+          }
+        }
+      }
+
+      binding.refreshGUI();
+      binding.setFinishedInit(true);
+      binding.setLoadingFromArchive(false);
+
+      /*
+       * ensure that any newly discovered features (e.g. RESNUM)
+       * are added to any open feature settings dialog
+       */
+      FeatureRenderer fr = getBinding().getFeatureRenderer(null);
+      if (fr != null)
+      {
+        fr.featuresAdded();
+      }
+
+      // refresh the sequence colours for the new structure(s)
+      for (AlignmentViewPanel ap : _colourwith)
+      {
+        binding.updateColours(ap);
+      }
+      // do superposition if asked to
+      if (alignAddedStructures)
+      {
+        new Thread(new Runnable()
+        {
+          @Override
+          public void run()
+          {
+            alignStructsWithAllAlignPanels();
+          }
+        }).start();
+      }
+      addingStructures = false;
+    }
+    _started = false;
+    worker = null;
+
+  }
+
+  /**
+   * Launch PyMOL. If we have a session file name, send PyMOL the command to
+   * open its saved session file.
+   */
+  void initPymol()
+  {
+    Desktop.addInternalFrame(this,
+            binding.getViewerTitle(getViewerName(), true),
+            getBounds().width, getBounds().height);
+
+    if (!binding.launchPymol())
+    {
+      JvOptionPane.showMessageDialog(Desktop.desktop,
+              MessageManager.getString("label.pymol_failed"),
+              MessageManager.getString("label.error_loading_file"),
+              JvOptionPane.ERROR_MESSAGE);
+      this.dispose();
+      return;
+    }
+
+    if (this.pymolSessionFile != null)
+    {
+      boolean opened = binding.openSession(pymolSessionFile);
+      if (!opened)
+      {
+        System.err.println("An error occurred opening PyMOL session file "
+                + pymolSessionFile);
+      }
+    }
+    // binding.startPymolListener();
+  }
+
+  @Override
+  public AAStructureBindingModel getBinding()
+  {
+    return binding;
+  }
+
+  @Override
+  public void closeViewer(boolean closePymol)
+  {
+    if (binding != null && binding.isPymolRunning())
+    {
+      if (!closePymol)
+      {
+        // TODO i18n (and pull up)
+        String prompt = MessageManager
+                .formatMessage("label.confirm_close_pymol", new Object[]
+                { binding.getViewerTitle(getViewerName(), false) });
+        prompt = JvSwingUtils.wrapTooltip(true, prompt);
+        int confirm = JvOptionPane.showConfirmDialog(this, prompt,
+                MessageManager.getString("label.close_viewer"),
+                JvOptionPane.YES_NO_CANCEL_OPTION);
+        /*
+         * abort closure if user hits escape or Cancel
+         */
+        if (confirm == JvOptionPane.CANCEL_OPTION
+                || confirm == JvOptionPane.CLOSED_OPTION)
+        {
+          return;
+        }
+        closePymol = confirm == JvOptionPane.YES_OPTION;
+      }
+      binding.closeViewer(closePymol);
+    }
+    setAlignmentPanel(null);
+    _aps.clear();
+    _alignwith.clear();
+    _colourwith.clear();
+    // TODO: check for memory leaks where instance isn't finalised because
+    // binding
+    // holds a reference to the window
+    binding = null;
+    dispose();
+  }
+
+  @Override
+  public String getStateInfo()
+  {
+    return null;
+  }
+
+  @Override
+  public ViewerType getViewerType()
+  {
+    return ViewerType.PYMOL;
+  }
+
+  @Override
+  protected String getViewerName()
+  {
+    return "PyMOL";
+  }
+
+}