JAL-3949 - refactor logging from jalview.bin.Cache to jalview.bin.Console
[jalview.git] / src / jalview / gui / AppJmol.java
index f340457..16d0dd7 100644 (file)
 /*
- * Jalview - A Sequence Alignment Editor and Viewer
- * Copyright (C) 2007 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle
- *
- * This program 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 2
+ * 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.
- *
- * This program 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.
- *
+ *  
+ * 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 this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
  */
 package jalview.gui;
 
-import java.util.regex.*;
-import java.util.*;
-import java.awt.*;
-import javax.swing.*;
-import javax.swing.event.*;
-import java.awt.event.*;
-import java.io.*;
-
-import jalview.jbgui.GStructureViewer;
-import jalview.datamodel.*;
-import jalview.gui.*;
-import jalview.structure.*;
+import java.util.Locale;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.JPanel;
+import javax.swing.JSplitPane;
+import javax.swing.SwingUtilities;
+import javax.swing.event.InternalFrameAdapter;
+import javax.swing.event.InternalFrameEvent;
+
+import jalview.api.AlignmentViewPanel;
+import jalview.bin.Console;
 import jalview.datamodel.PDBEntry;
-import jalview.io.*;
-import jalview.schemes.*;
-import jalview.ws.ebi.EBIFetchClient;
-
-import org.jmol.api.*;
-import org.jmol.adapter.smarter.SmarterJmolAdapter;
-import org.jmol.popup.*;
-
-
-public class AppJmol
-    extends GStructureViewer
-    implements StructureListener, JmolStatusListener, Runnable
-
+import jalview.datamodel.SequenceI;
+import jalview.datamodel.StructureViewerModel;
+import jalview.datamodel.StructureViewerModel.StructureData;
+import jalview.fts.service.alphafold.AlphafoldRestClient;
+import jalview.gui.ImageExporter.ImageWriterI;
+import jalview.gui.StructureViewer.ViewerType;
+import jalview.structure.StructureCommand;
+import jalview.structures.models.AAStructureBindingModel;
+import jalview.util.BrowserLauncher;
+import jalview.util.ImageMaker;
+import jalview.util.MessageManager;
+import jalview.util.Platform;
+
+public class AppJmol extends StructureViewerBase
 {
-  JmolViewer viewer;
-  JmolPopup jmolpopup;
-  ScriptWindow scriptWindow;
-  PDBEntry pdbentry;
-  SequenceI[] sequence;
-  String [] chains;
-  StructureSelectionManager ssm;
-  JSplitPane splitPane;
-  RenderPanel renderPanel;
-  AlignmentPanel ap;
-  String fileLoadingError;
-  boolean colourBySequence = true;
-  boolean loadingFromArchive = false;
-  Vector atomsPicked = new Vector();
-
-  public AppJmol(String file, String id,
-                 SequenceI[] seq,
-                 AlignmentPanel ap,
-                 String loadStatus,
-                 Rectangle bounds)
-  {
-    loadingFromArchive = true;
-    pdbentry = new PDBEntry();
-    pdbentry.setFile(file);
-    pdbentry.setId(id);
-    this.chains = chains;
-    this.sequence = seq;
-    this.ap = ap;
-    this.setBounds(bounds);
-
-    colourBySequence = false;
-    seqColour.setSelected(false);
-
-    jalview.gui.Desktop.addInternalFrame(this, "Loading File",
-                                         bounds.width,bounds.height);
-
-    initJmol(loadStatus);
-
-    this.addInternalFrameListener(new InternalFrameAdapter()
-    {
-      public void internalFrameClosing(InternalFrameEvent internalFrameEvent)
-      {
-        closeViewer();
-      }
-    });
-  }
+  // ms to wait for Jmol to load files
+  private static final int JMOL_LOAD_TIMEOUT = 20000;
 
-public synchronized void addSequence(SequenceI [] seq)
-   {
-    Vector v = new Vector();
-     for(int i=0; i<sequence.length; i++)
-       v.addElement(sequence[i]);
+  private static final String SPACE = " ";
 
-     for(int i=0; i<seq.length; i++)
-      if(!v.contains(seq[i]))
-          v.addElement(seq[i]);
+  private static final String QUOTE = "\"";
 
-     SequenceI [] tmp = new SequenceI[v.size()];
-     v.copyInto(tmp);
-     sequence = tmp;
-   }
+  AppJmolBinding jmb;
 
-  public AppJmol(PDBEntry pdbentry, SequenceI[] seq, String [] chains, AlignmentPanel ap)
-  {
-    //////////////////////////////////
-    //Is the pdb file already loaded?
-    String alreadyMapped = StructureSelectionManager
-        .getStructureSelectionManager()
-        .alreadyMappedToFile(pdbentry.getId());
-
-    if (alreadyMapped != null)
-    {
-      int option = JOptionPane.showInternalConfirmDialog(Desktop.desktop,
-          pdbentry.getId() + " is already displayed."
-          + "\nDo you want to map sequences to the visible structure?",
-          "Map Sequences to Visible Window: " + pdbentry.getId(),
-          JOptionPane.YES_NO_OPTION);
+  JPanel scriptWindow;
 
-      if (option == JOptionPane.YES_OPTION)
-      {
-        StructureSelectionManager.getStructureSelectionManager()
-            .setMapping(seq, chains, alreadyMapped, AppletFormatAdapter.FILE);
-        if (ap.seqPanel.seqCanvas.fr!=null) {
-          ap.seqPanel.seqCanvas.fr.featuresAdded();
-          ap.paintAlignment(true);
-        }
-
-        //Now this AppJmol is mapped to new sequences. We must add them to
-        // the exisiting array
-        JInternalFrame [] frames = Desktop.instance.getAllFrames();
+  JSplitPane splitPane;
 
-        for(int i=0; i<frames.length; i++)
-        {
-          if(frames[i] instanceof AppJmol)
-          {
-           AppJmol topJmol = ((AppJmol)frames[i]);
-           if(topJmol.pdbentry.getFile().equals(alreadyMapped))
-           {
-             topJmol.addSequence(seq);
-             break;
-           }
-          }
-        }
+  RenderPanel renderPanel;
 
-        return;
-      }
+  /**
+   * 
+   * @param files
+   * @param ids
+   * @param seqs
+   * @param ap
+   * @param usetoColour
+   *          - add the alignment panel to the list used for colouring these
+   *          structures
+   * @param useToAlign
+   *          - add the alignment panel to the list used for aligning these
+   *          structures
+   * @param leaveColouringToJmol
+   *          - do not update the colours from any other source. Jmol is
+   *          handling them
+   * @param loadStatus
+   * @param bounds
+   * @param viewid
+   */
+  public AppJmol(StructureViewerModel viewerModel, AlignmentPanel ap,
+          String sessionFile, String viewid)
+  {
+    Map<File, StructureData> pdbData = viewerModel.getFileData();
+    PDBEntry[] pdbentrys = new PDBEntry[pdbData.size()];
+    SequenceI[][] seqs = new SequenceI[pdbData.size()][];
+    int i = 0;
+    for (StructureData data : pdbData.values())
+    {
+      PDBEntry pdbentry = new PDBEntry(data.getPdbId(), null,
+              PDBEntry.Type.PDB, data.getFilePath());
+      pdbentrys[i] = pdbentry;
+      List<SequenceI> sequencesForPdb = data.getSeqList();
+      seqs[i] = sequencesForPdb
+              .toArray(new SequenceI[sequencesForPdb.size()]);
+      i++;
     }
-    ///////////////////////////////////
 
-    this.ap = ap;
-    this.pdbentry = pdbentry;
-    this.sequence = seq;
+    // TODO: check if protocol is needed to be set, and if chains are
+    // autodiscovered.
+    jmb = new AppJmolBinding(this, ap.getStructureSelectionManager(),
+            pdbentrys, seqs, null);
 
-    jalview.gui.Desktop.addInternalFrame(this, "Loading File", 400, 400);
-
-    if (pdbentry.getFile() != null)
+    jmb.setLoadingFromArchive(true);
+    addAlignmentPanel(ap);
+    if (viewerModel.isAlignWithPanel())
     {
-      initJmol("load \""+pdbentry.getFile()+"\"");
+      useAlignmentPanelForSuperposition(ap);
     }
-    else
+    initMenus();
+    boolean useToColour = viewerModel.isColourWithAlignPanel();
+    boolean leaveColouringToJmol = viewerModel.isColourByViewer();
+    if (leaveColouringToJmol || !useToColour)
+    {
+      jmb.setColourBySequence(false);
+      seqColour.setSelected(false);
+      viewerColour.setSelected(true);
+    }
+    else if (useToColour)
     {
-      Thread worker = new Thread(this);
-      worker.start();
+      useAlignmentPanelForColourbyseq(ap);
+      jmb.setColourBySequence(true);
+      seqColour.setSelected(true);
+      viewerColour.setSelected(false);
     }
 
+    this.setBounds(viewerModel.getX(), viewerModel.getY(),
+            viewerModel.getWidth(), viewerModel.getHeight());
+    setViewId(viewid);
+
     this.addInternalFrameListener(new InternalFrameAdapter()
     {
-      public void internalFrameClosing(InternalFrameEvent internalFrameEvent)
+      @Override
+      public void internalFrameClosing(
+              InternalFrameEvent internalFrameEvent)
       {
-        closeViewer();
+        closeViewer(false);
       }
     });
+    StringBuilder cmd = new StringBuilder();
+    cmd.append("load FILES ").append(QUOTE)
+            .append(Platform.escapeBackslashes(sessionFile)).append(QUOTE);
+    initJmol(cmd.toString());
   }
 
-
-  void initJmol(String command)
-  {
-    renderPanel = new RenderPanel();
-
-    this.getContentPane().add(renderPanel, java.awt.BorderLayout.CENTER);
-
-    StringBuffer title = new StringBuffer(sequence[0].getName() + ":" +
-                                          pdbentry.getId());
-
-    if (pdbentry.getProperty() != null)
-    {
-      if (pdbentry.getProperty().get("method") != null)
-      {
-        title.append(" Method: ");
-        title.append(pdbentry.getProperty().get("method"));
-      }
-      if (pdbentry.getProperty().get("chains") != null)
-      {
-        title.append(" Chain:");
-        title.append(pdbentry.getProperty().get("chains"));
-      }
-    }
-
-    this.setTitle(title.toString());
-
-    viewer = org.jmol.api.JmolViewer.allocateViewer(renderPanel,
-        new SmarterJmolAdapter());
-
-
-    viewer.setAppletContext("", null, null, "");
-
-    viewer.setJmolStatusListener(this);
-
-    jmolpopup = JmolPopup.newJmolPopup(viewer);
-
-    viewer.evalStringQuiet(command);
-  }
-
-
-  void setChainMenuItems(Vector chains)
+  @Override
+  protected void initMenus()
   {
-    chainMenu.removeAll();
-
-    JMenuItem menuItem = new JMenuItem("All");
-    menuItem.addActionListener(new ActionListener()
-        {
-          public void actionPerformed(ActionEvent evt)
-          {
-            allChainsSelected = true;
-            for(int i=0; i<chainMenu.getItemCount(); i++)
-            {
-              if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
-                ( (JCheckBoxMenuItem) chainMenu.getItem(i)).setSelected(true);
-            }
-            centerViewer();
-            allChainsSelected = false;
-          }
-        });
+    super.initMenus();
 
-    chainMenu.add(menuItem);
-
-    for (int c = 0; c < chains.size(); c++)
-    {
-      menuItem = new JCheckBoxMenuItem(chains.elementAt(c).toString(), true);
-      menuItem.addItemListener(new ItemListener()
-      {
-        public void itemStateChanged(ItemEvent evt)
-        {
-          if (!allChainsSelected)
-            centerViewer();
-        }
-      });
-
-      chainMenu.add(menuItem);
-    }
+    viewerColour
+            .setText(MessageManager.getString("label.colour_with_jmol"));
+    viewerColour.setToolTipText(MessageManager
+            .getString("label.let_jmol_manage_structure_colours"));
   }
 
-  boolean allChainsSelected = false;
-  void centerViewer()
+  /**
+   * display a single PDB structure in a new Jmol view
+   * 
+   * @param pdbentry
+   * @param seq
+   * @param chains
+   * @param ap
+   */
+  public AppJmol(PDBEntry pdbentry, SequenceI[] seq, String[] chains,
+          final AlignmentPanel ap)
   {
-    StringBuffer cmd = new StringBuffer();
-    for(int i=0; i<chainMenu.getItemCount(); i++)
-    {
-      if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
-      {
-       JCheckBoxMenuItem item = (JCheckBoxMenuItem) chainMenu.getItem(i);
-       if(item.isSelected())
-         cmd.append(":"+item.getText()+" or ");
-      }
-    }
-
-    if (cmd.length() > 0)
-      cmd.setLength(cmd.length() - 4);
+    setProgressIndicator(ap.alignFrame);
 
-    viewer.evalStringQuiet("select *;restrict "
-                      +cmd+";cartoon;center "+cmd);
+    openNewJmol(ap, alignAddedStructures, new PDBEntry[] { pdbentry },
+            new SequenceI[][]
+            { seq });
   }
 
-  void closeViewer()
+  private void openNewJmol(AlignmentPanel ap, boolean alignAdded,
+          PDBEntry[] pdbentrys,
+          SequenceI[][] seqs)
   {
-    viewer.setModeMouse(org.jmol.viewer.JmolConstants.MOUSE_NONE);
-    viewer.evalStringQuiet("zap");
-    viewer.setJmolStatusListener(null);
-    viewer = null;
-
-    //We'll need to find out what other
-    // listeners need to be shut down in Jmol
-    StructureSelectionManager
-        .getStructureSelectionManager()
-        .removeStructureViewerListener(this, pdbentry.getFile());
-  }
+    setProgressIndicator(ap.alignFrame);
+    jmb = new AppJmolBinding(this, ap.getStructureSelectionManager(),
+            pdbentrys, seqs, null);
+    addAlignmentPanel(ap);
+    useAlignmentPanelForColourbyseq(ap);
 
-  public void run()
-  {
-    try
+    alignAddedStructures = alignAdded;
+    if (pdbentrys.length > 1)
     {
-      EBIFetchClient ebi = new EBIFetchClient();
-      String query = "pdb:" + pdbentry.getId();
-      pdbentry.setFile(ebi.fetchDataAsFile(query, "default", "raw")
-                       .getAbsolutePath());
-      initJmol("load "+pdbentry.getFile());
+      useAlignmentPanelForSuperposition(ap);
     }
-    catch (Exception ex)
-    {
-      ex.printStackTrace();
-    }
-  }
 
-  public void pdbFile_actionPerformed(ActionEvent actionEvent)
-  {
-    JalviewFileChooser chooser = new JalviewFileChooser(
-        jalview.bin.Cache.getProperty(
-            "LAST_DIRECTORY"));
-
-    chooser.setFileView(new JalviewFileView());
-    chooser.setDialogTitle("Save PDB File");
-    chooser.setToolTipText("Save");
-
-    int value = chooser.showSaveDialog(this);
+    jmb.setColourBySequence(true);
+    setSize(400, 400); // probably should be a configurable/dynamic default here
+    initMenus();
+    addingStructures = false;
+    worker = new Thread(this);
+    worker.start();
 
-    if (value == JalviewFileChooser.APPROVE_OPTION)
+    this.addInternalFrameListener(new InternalFrameAdapter()
     {
-      try
-      {
-        BufferedReader in = new BufferedReader(new FileReader(pdbentry.getFile()));
-        File outFile = chooser.getSelectedFile();
-
-        PrintWriter out = new PrintWriter(new FileOutputStream(outFile));
-        String data;
-        while ( (data = in.readLine()) != null)
-        {
-          if (
-              ! (data.indexOf("<PRE>") > -1 || data.indexOf("</PRE>") > -1)
-              )
-          {
-            out.println(data);
-          }
-        }
-        out.close();
-      }
-      catch (Exception ex)
+      @Override
+      public void internalFrameClosing(
+              InternalFrameEvent internalFrameEvent)
       {
-        ex.printStackTrace();
+        closeViewer(false);
       }
-    }
-  }
+    });
 
-  public void viewMapping_actionPerformed(ActionEvent actionEvent)
-  {
-    jalview.gui.CutAndPasteTransfer cap = new jalview.gui.CutAndPasteTransfer();
-    jalview.gui.Desktop.addInternalFrame(cap, "PDB - Sequence Mapping", 550,
-                                         600);
-    cap.setText(
-        StructureSelectionManager.getStructureSelectionManager().printMapping(
-            pdbentry.getFile())
-        );
   }
 
   /**
-   * DOCUMENT ME!
-   *
-   * @param e DOCUMENT ME!
+   * create a new Jmol containing several structures optionally superimposed
+   * using the given alignPanel.
+   * 
+   * @param ap
+   * @param alignAdded
+   *          - true to superimpose
+   * @param pe
+   * @param seqs
    */
-  public void eps_actionPerformed(ActionEvent e)
+  public AppJmol(AlignmentPanel ap, boolean alignAdded, PDBEntry[] pe,
+          SequenceI[][] seqs)
   {
-    makePDBImage(jalview.util.ImageMaker.EPS);
+    openNewJmol(ap, alignAdded, pe, seqs);
   }
 
-  /**
-   * DOCUMENT ME!
-   *
-   * @param e DOCUMENT ME!
-   */
-  public void png_actionPerformed(ActionEvent e)
-  {
-    makePDBImage(jalview.util.ImageMaker.PNG);
-  }
 
-  void makePDBImage(int type)
+  void initJmol(String command)
   {
-    int width = getWidth();
-    int height = getHeight();
-
-    jalview.util.ImageMaker im;
-
-    if (type == jalview.util.ImageMaker.PNG)
-    {
-      im = new jalview.util.ImageMaker(this,
-                                       jalview.util.ImageMaker.PNG,
-                                       "Make PNG image from view",
-                                       width, height,
-                                       null, null);
-    }
-    else
+    jmb.setFinishedInit(false);
+    renderPanel = new RenderPanel();
+    // TODO: consider waiting until the structure/view is fully loaded before
+    // displaying
+    this.getContentPane().add(renderPanel, java.awt.BorderLayout.CENTER);
+    jalview.gui.Desktop.addInternalFrame(this, jmb.getViewerTitle(),
+            getBounds().width, getBounds().height);
+    if (scriptWindow == null)
     {
-      im = new jalview.util.ImageMaker(this,
-                                       jalview.util.ImageMaker.EPS,
-                                       "Make EPS file from view",
-                                       width, height,
-                                       null, this.getTitle());
+      BorderLayout bl = new BorderLayout();
+      bl.setHgap(0);
+      bl.setVgap(0);
+      scriptWindow = new JPanel(bl);
+      scriptWindow.setVisible(false);
     }
 
-    if (im.getGraphics() != null)
+    jmb.allocateViewer(renderPanel, true, "", null, null, "", scriptWindow,
+            null);
+    // jmb.newJmolPopup("Jmol");
+    if (command == null)
     {
-      Rectangle rect = new Rectangle(width, height);
-      viewer.renderScreenImage(im.getGraphics(),
-                               rect.getSize(), rect);
-      im.writeImage();
+      command = "";
     }
+    jmb.executeCommand(new StructureCommand(command), false);
+    jmb.executeCommand(new StructureCommand("set hoverDelay=0.1"), false);
+    jmb.setFinishedInit(true);
   }
 
-
-  public void seqColour_actionPerformed(ActionEvent actionEvent)
-  {
-    lastCommand = null;
-    colourBySequence = seqColour.isSelected();
-    colourBySequence(ap.alignFrame.alignPanel);
-  }
-
-  public void chainColour_actionPerformed(ActionEvent actionEvent)
-  {
-    colourBySequence = false;
-    seqColour.setSelected(false);
-    viewer.evalStringQuiet("select *;color chain");
-  }
-
-  public void chargeColour_actionPerformed(ActionEvent actionEvent)
-  {
-    colourBySequence = false;
-    seqColour.setSelected(false);
-    viewer.evalStringQuiet("select *;color white;select ASP,GLU;color red;"
-                      +"select LYS,ARG;color blue;select CYS;color yellow");
-  }
-
-  public void zappoColour_actionPerformed(ActionEvent actionEvent)
-  {
-    setJalviewColourScheme(new ZappoColourScheme());
-  }
-
-  public void taylorColour_actionPerformed(ActionEvent actionEvent)
-  {
-    setJalviewColourScheme(new TaylorColourScheme());
-  }
-
-  public void hydroColour_actionPerformed(ActionEvent actionEvent)
-  {
-    setJalviewColourScheme(new HydrophobicColourScheme());
-  }
-
-  public void helixColour_actionPerformed(ActionEvent actionEvent)
-  {
-    setJalviewColourScheme(new HelixColourScheme());
-  }
-
-  public void strandColour_actionPerformed(ActionEvent actionEvent)
-  {
-    setJalviewColourScheme(new StrandColourScheme());
-  }
-
-  public void turnColour_actionPerformed(ActionEvent actionEvent)
-  {
-    setJalviewColourScheme(new TurnColourScheme());
-  }
-
-  public void buriedColour_actionPerformed(ActionEvent actionEvent)
-  {
-    setJalviewColourScheme(new BuriedColourScheme());
-  }
-
-  public void setJalviewColourScheme(ColourSchemeI cs)
+  @Override
+  public void run()
   {
-    colourBySequence = false;
-    seqColour.setSelected(false);
-
-    if(cs==null)
-      return;
-
-    String res;
-    int index;
-    Color col;
-
-    Enumeration en = ResidueProperties.aa3Hash.keys();
-    StringBuffer command = new StringBuffer("select *;color white;");
-    while(en.hasMoreElements())
+    _started = true;
+    try
     {
-      res = en.nextElement().toString();
-      index = ((Integer) ResidueProperties.aa3Hash.get(res)).intValue();
-      if(index>20)
-        continue;
-
-      col = cs.findColour(ResidueProperties.aa[index].charAt(0));
-
-      command.append("select "+res+";color["
-                        + col.getRed() + ","
-                        + col.getGreen() + ","
-                        + col.getBlue() + "];");
-    }
-
-    viewer.evalStringQuiet(command.toString());
-  }
-
-  public void userColour_actionPerformed(ActionEvent actionEvent)
-  {
-    new UserDefinedColours(this, null);
-  }
-
-  public void backGround_actionPerformed(ActionEvent actionEvent)
-  {
-    java.awt.Color col = JColorChooser.showDialog(this,
-                                                  "Select Background Colour",
-                                                  null);
-
-    if (col != null)
+      List<String> files = jmb.fetchPdbFiles(this);
+      if (files.size() > 0)
+      {
+        showFilesInViewer(files);
+      }
+    } finally
     {
-      viewer.evalStringQuiet("background ["
-                        + col.getRed() + ","
-                        + col.getGreen() + ","
-                        + col.getBlue() + "];");
+      _started = false;
+      worker = null;
     }
   }
 
-
-  public void jmolHelp_actionPerformed(ActionEvent actionEvent)
-  {
-       try{
-         jalview.util.BrowserLauncher.openURL(
-             "http://jmol.sourceforge.net/docs/JmolUserGuide/");
-       }catch(Exception ex){}
-   }
-
-
-  //////////////////////////////////
-  ///StructureListener
-  public String getPdbFile()
-  {
-    return pdbentry.getFile();
-  }
-
-  Pattern pattern = Pattern.compile(
-      "\\[(.*)\\]([0-9]+)(:[a-zA-Z]*)?\\.([a-zA-Z]+)(/[0-9]*)?"
-      );
-
-  String lastMessage;
-  public void mouseOverStructure(int atomIndex, String strInfo)
+  /**
+   * Either adds the given files to a structure viewer or opens a new viewer to
+   * show them
+   * 
+   * @param files
+   *          list of absolute paths to structure files
+   */
+  void showFilesInViewer(List<String> files)
   {
-    Matcher matcher = pattern.matcher(strInfo);
-    matcher.find();
-    matcher.group(1);
-    int pdbResNum = Integer.parseInt(matcher.group(2));
-    String chainId = matcher.group(3);
-
-    if (chainId != null)
-      chainId = chainId.substring(1, chainId.length());
-    else
+    long lastnotify = jmb.getLoadNotifiesHandled();
+    StringBuilder fileList = new StringBuilder();
+    for (String s : files)
     {
-      chainId = " ";
+      fileList.append(SPACE).append(QUOTE)
+              .append(Platform.escapeBackslashes(s)).append(QUOTE);
     }
+    String filesString = fileList.toString();
 
-    if (lastMessage == null || !lastMessage.equals(strInfo))
-    {
-      ssm.mouseOverStructure(pdbResNum, chainId, pdbentry.getFile());
-    }
-    lastMessage = strInfo;
-  }
-
-  StringBuffer resetLastRes = new StringBuffer();
-  StringBuffer eval = new StringBuffer();
-
-  public void highlightAtom(int atomIndex, int pdbResNum, String chain, String pdbfile)
-  {
-    if (!pdbfile.equals(pdbentry.getFile()))
-      return;
-
-    if (resetLastRes.length() > 0)
+    if (!addingStructures)
     {
-      viewer.evalStringQuiet(resetLastRes.toString());
+      try
+      {
+        initJmol("load FILES " + filesString);
+      } catch (OutOfMemoryError oomerror)
+      {
+        new OOMWarning("When trying to open the Jmol viewer!", oomerror);
+        Console.debug("File locations are " + filesString);
+      } catch (Exception ex)
+      {
+        Console.error("Couldn't open Jmol viewer!", ex);
+        ex.printStackTrace();
+        return;
+      }
     }
-
-    eval.setLength(0);
-    eval.append("select " + pdbResNum);
-
-    resetLastRes.setLength(0);
-    resetLastRes.append("select " + pdbResNum);
-
-    if (!chain.equals(" "))
+    else
     {
-      eval.append(":" + chain);
-      resetLastRes.append(":" + chain);
-    }
-
-    eval.append(";wireframe 100;"+eval.toString()+".CA;");
-
-    resetLastRes.append(";wireframe 0;"+resetLastRes.toString()+".CA;spacefill 0;");
-
-    eval.append("spacefill 200;select none");
-
-    viewer.evalStringQuiet(eval.toString());
-  }
-
-  public Color getColour(int atomIndex, int pdbResNum, String chain, String pdbfile)
-  {
-    if (!pdbfile.equals(pdbentry.getFile()))
-      return null;
-
-    return new Color(viewer.getAtomArgb(atomIndex));
-  }
-
-  public void updateColours(Object source)
-  {
-    colourBySequence( (AlignmentPanel) source);
-  }
-
-
-//End StructureListener
-////////////////////////////
-
-  String lastCommand;
-  FeatureRenderer fr=null;
-  public void colourBySequence(AlignmentPanel sourceap)
-  {
-    this.ap = sourceap;
-
-    if(!colourBySequence || ap.alignFrame.getCurrentView()!=ap.av)
-      return;
-
-    StructureMapping[] mapping = ssm.getMapping(pdbentry.getFile());
+      StringBuilder cmd = new StringBuilder();
+      cmd.append("loadingJalviewdata=true\nload APPEND ");
+      cmd.append(filesString);
+      cmd.append("\nloadingJalviewdata=null");
+      final StructureCommand command = new StructureCommand(cmd.toString());
+      lastnotify = jmb.getLoadNotifiesHandled();
 
-    if (mapping.length < 1)
-     return;
-
-
-    SequenceRenderer sr = new SequenceRenderer(ap.av);
-
-    boolean showFeatures = false;
-
-    if (ap.av.showSequenceFeatures)
-    {
-      showFeatures = true;
-      if (fr == null)
+      try
       {
-        fr = new jalview.gui.FeatureRenderer(ap);
+        jmb.executeCommand(command, false);
+      } catch (OutOfMemoryError oomerror)
+      {
+        new OOMWarning("When trying to add structures to the Jmol viewer!",
+                oomerror);
+        Console.debug("File locations are " + filesString);
+        return;
+      } catch (Exception ex)
+      {
+        Console.error("Couldn't add files to Jmol viewer!", ex);
+        ex.printStackTrace();
+        return;
       }
-
-      fr.transferSettings(ap.seqPanel.seqCanvas.getFeatureRenderer());
     }
 
-    StringBuffer command = new StringBuffer();
-
-    int lastPos = -1;
-    for (int sp,s = 0; s < sequence.length; s++)
+    // need to wait around until script has finished
+    int waitMax = JMOL_LOAD_TIMEOUT;
+    int waitFor = 35;
+    int waitTotal = 0;
+    while (addingStructures ? lastnotify >= jmb.getLoadNotifiesHandled()
+            : !(jmb.isFinishedInit() && jmb.getStructureFiles() != null
+                    && jmb.getStructureFiles().length == files.size()))
     {
-      for (int m = 0; m < mapping.length; m++)
+      try
       {
-        if (mapping[m].getSequence() == sequence[s]
-            && (sp=ap.av.alignment.findIndex(sequence[s]))>-1)
-        {
-          SequenceI asp = ap.av.alignment.getSequenceAt(sp);
-          for (int r = 0; r < asp.getLength(); r++)
-          {
-            // No mapping to gaps in sequence.
-            if (jalview.util.Comparison.isGap(asp.getCharAt(r)))
-            {
-              continue;
-            }
-            int pos = mapping[m].getPDBResNum(
-                    asp.findPosition(r));
-
-            if (pos < 1 || pos==lastPos)
-              continue;
-
-            lastPos = pos;
-
-            Color col = sr.getResidueBoxColour(asp, r);
-
-            if (showFeatures)
-              col = fr.findFeatureColour(col, asp, r);
-
-            if (command.toString().endsWith(":" + mapping[m].getChain()+
-                                            ";color["
-                                            + col.getRed() + ","
-                                            + col.getGreen() + ","
-                                            + col.getBlue() + "]"))
-            {
-              command = condenseCommand(command, pos);
-              continue;
-            }
-
-            command.append(";select " + pos);
-
-            if (!mapping[m].getChain().equals(" "))
-            {
-              command.append(":" + mapping[m].getChain());
-            }
-
-            command.append(";color["
-                             + col.getRed() + ","
-                             + col.getGreen() + ","
-                             + col.getBlue() + "]");
+        Console.debug("Waiting around for jmb notify.");
+        waitTotal += waitFor;
 
-          }
-          break;
-        }
+        // Thread.sleep() throws an exception in JS
+        Thread.sleep(waitFor);
+      } catch (Exception e)
+      {
+      }
+      if (waitTotal > waitMax)
+      {
+        System.err.println("Timed out waiting for Jmol to load files after "
+                + waitTotal + "ms");
+        // System.err.println("finished: " + jmb.isFinishedInit()
+        // + "; loaded: " + Arrays.toString(jmb.getPdbFile())
+        // + "; files: " + files.toString());
+        jmb.getStructureFiles();
+        break;
       }
     }
 
-    if (lastCommand == null || !lastCommand.equals(command.toString()))
-    {
-      viewer.evalStringQuiet(command.toString());
-    }
-    lastCommand = command.toString();
-  }
-
-  StringBuffer condenseCommand(StringBuffer command, int pos)
-  {
-    StringBuffer sb = new StringBuffer(command.substring(0, command.lastIndexOf("select")+7));
-
-    command.delete(0, sb.length());
-
-    String start;
-
-    if (command.indexOf("-") > -1)
+    // refresh the sequence colours for the new structure(s)
+    for (AlignmentViewPanel ap : _colourwith)
     {
-      start = command.substring(0,command.indexOf("-"));
+      jmb.updateColours(ap);
     }
-    else
+    // do superposition if asked to
+    if (alignAddedStructures)
     {
-      start = command.substring(0, command.indexOf(":"));
+      alignAddedStructures();
     }
-
-    sb.append(start+"-"+pos+command.substring(command.indexOf(":")));
-
-    return sb;
+    addingStructures = false;
   }
 
-  /////////////////////////////////
-  //JmolStatusListener
-
-  public String eval(String strEval)
-  {
-   // System.out.println(strEval);
-   //"# 'eval' is implemented only for the applet.";
-    return null;
-  }
-
-  public void createImage(String file, String type, int quality)
-  {
-    System.out.println("JMOL CREATE IMAGE");
-  }
-
-  public void setCallbackFunction(String callbackType,
-                                  String callbackFunction)
-  {}
-
-  public void notifyFileLoaded(String fullPathName, String fileName,
-                               String modelName, Object clientFile,
-                               String errorMsg)
+  /**
+   * Queues a thread to align structures with Jalview alignments
+   */
+  void alignAddedStructures()
   {
-    if(errorMsg!=null)
+    javax.swing.SwingUtilities.invokeLater(new Runnable()
     {
-      fileLoadingError = errorMsg;
-      repaint();
-      return;
-    }
-
-    fileLoadingError = null;
-
-    if (fileName != null)
-    {
-
-      //FILE LOADED OK
-      ssm = StructureSelectionManager.getStructureSelectionManager();
-      MCview.PDBfile pdbFile = ssm.setMapping(sequence,chains,pdbentry.getFile(), AppletFormatAdapter.FILE);
-      ssm.addStructureViewerListener(this);
-      Vector chains = new Vector();
-      for(int i=0; i<pdbFile.chains.size(); i++)
+      @Override
+      public void run()
       {
-        chains.addElement(((MCview.PDBChain)pdbFile.chains.elementAt(i)).id);
-      }
-      setChainMenuItems(chains);
-
-      jmolpopup.updateComputedMenus();
-
-      if(!loadingFromArchive)
-      {
-        viewer.evalStringQuiet(
-             "select backbone;restrict;cartoon;wireframe off;spacefill off");
-
-        colourBySequence(ap);
+        if (jmb.jmolViewer.isScriptExecuting())
+        {
+          SwingUtilities.invokeLater(this);
+          try
+          {
+            Thread.sleep(5);
+          } catch (InterruptedException q)
+          {
+          }
+          return;
+        }
+        else
+        {
+          alignStructsWithAllAlignPanels();
+        }
       }
-      if (fr!=null)
-        fr.featuresAdded();
-
-      loadingFromArchive = false;
-    }
-    else
-      return;
-  }
-
-  public void notifyFrameChanged(int frameNo)
-  {
-    boolean isAnimationRunning = (frameNo <= -2);
-  }
-
-  public void notifyScriptStart(String statusMessage, String additionalInfo)
-  {}
-
-  public void sendConsoleEcho(String strEcho)
-  {
-    if (scriptWindow != null)
-      scriptWindow.sendConsoleEcho(strEcho);
-  }
-
-  public void sendConsoleMessage(String strStatus)
-  {
-    if (scriptWindow != null)
-      scriptWindow.sendConsoleMessage(strStatus);
-  }
-
-  public void notifyScriptTermination(String strStatus, int msWalltime)
-  {
-    if (scriptWindow != null)
-      scriptWindow.notifyScriptTermination(strStatus, msWalltime);
-  }
+    });
 
-  public void handlePopupMenu(int x, int y)
-  {
-    jmolpopup.show(x, y);
   }
 
-  public void notifyNewPickingModeMeasurement(int iatom, String strMeasure)
+  /**
+   * Outputs the Jmol viewer image as an image file, after prompting the user to
+   * choose a file and (for EPS) choice of Text or Lineart character rendering
+   * (unless a preference for this is set)
+   * 
+   * @param type
+   */
+  @Override
+  public void makePDBImage(ImageMaker.TYPE type)
   {
-    notifyAtomPicked(iatom, strMeasure);
+    int width = getWidth();
+    int height = getHeight();
+    ImageWriterI writer = new ImageWriterI()
+    {
+      @Override
+      public void exportImage(Graphics g) throws Exception
+      {
+        jmb.jmolViewer.renderScreenImage(g, width, height);
+      }
+    };
+    String view = MessageManager.getString("action.view").toLowerCase(Locale.ROOT);
+    ImageExporter exporter = new ImageExporter(writer,
+            getProgressIndicator(), type, getTitle());
+    exporter.doExport(null, this, width, height, view);
   }
 
-  public void notifyNewDefaultModeMeasurement(int count, String strInfo)
-  {}
-
-  public void notifyAtomPicked(int atomIndex, String strInfo)
+  @Override
+  public void showHelp_actionPerformed()
   {
-    Matcher matcher = pattern.matcher(strInfo);
-    matcher.find();
-
-    matcher.group(1);
-    String resnum = new String(matcher.group(2));
-    String chainId = matcher.group(3);
-
-    String picked = resnum;
-
-    if (chainId != null)
-      picked+=(":"+chainId.substring(1, chainId.length()));
-
-    picked+=".CA";
-
-
-    if (!atomsPicked.contains(picked))
+    try
     {
-      if(chainId!=null)
-      viewer.evalString("select "+picked+";label %n %r:%c");
-    else
-      viewer.evalString("select "+picked+";label %n %r");
-      atomsPicked.addElement(picked);
-    }
-    else
+      BrowserLauncher // BH 2018
+              .openURL("http://wiki.jmol.org");//http://jmol.sourceforge.net/docs/JmolUserGuide/");
+    } catch (Exception ex)
     {
-      viewer.evalString("select "+picked+";label off");
-      atomsPicked.removeElement(picked);
+      System.err.println("Show Jmol help failed with: " + ex.getMessage());
     }
-
-    if (scriptWindow != null)
-    {
-      scriptWindow.sendConsoleMessage(strInfo);
-      scriptWindow.sendConsoleMessage("\n");
-    }
-  }
-
-  public void notifyAtomHovered(int atomIndex, String strInfo)
-  {
-    mouseOverStructure(atomIndex, strInfo);
   }
 
-  public void sendSyncScript(String script, String appletName)
-  {}
-
-  public void showUrl(String url)
-  {}
-
+  @Override
   public void showConsole(boolean showConsole)
   {
-    if (scriptWindow == null)
-      scriptWindow = new ScriptWindow(this);
-
-    if(showConsole)
+    if (showConsole)
     {
-      if(splitPane==null)
+      if (splitPane == null)
       {
         splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
         splitPane.setTopComponent(renderPanel);
         splitPane.setBottomComponent(scriptWindow);
         this.getContentPane().add(splitPane, BorderLayout.CENTER);
+        splitPane.setDividerLocation(getHeight() - 200);
+        scriptWindow.setVisible(true);
+        scriptWindow.validate();
+        splitPane.validate();
       }
 
-      splitPane.setDividerLocation(getHeight()-200);
-      splitPane.validate();
     }
     else
     {
       if (splitPane != null)
+      {
         splitPane.setVisible(false);
+      }
 
       splitPane = null;
 
@@ -917,48 +488,73 @@ public synchronized void addSequence(SequenceI [] seq)
     validate();
   }
 
-  public float functionXY(String functionName, int x, int y)
-  {
-    return 0;
-  }
-
-  ///End JmolStatusListener
-  ///////////////////////////////
-
-
-  class RenderPanel
-      extends JPanel
+  class RenderPanel extends JPanel
   {
     final Dimension currentSize = new Dimension();
-    final Rectangle rectClip = new Rectangle();
 
+    @Override
     public void paintComponent(Graphics g)
     {
       getSize(currentSize);
-      g.getClipBounds(rectClip);
 
-      if (viewer == null)
+      if (jmb != null && jmb.hasFileLoadingError())
       {
         g.setColor(Color.black);
         g.fillRect(0, 0, currentSize.width, currentSize.height);
         g.setColor(Color.white);
         g.setFont(new Font("Verdana", Font.BOLD, 14));
-        g.drawString("Retrieving PDB data....", 20, currentSize.height / 2);
+        g.drawString(MessageManager.getString("label.error_loading_file")
+                + "...", 20, currentSize.height / 2);
+        StringBuffer sb = new StringBuffer();
+        int lines = 0;
+        for (int e = 0; e < jmb.getPdbCount(); e++)
+        {
+          sb.append(jmb.getPdbEntry(e).getId());
+          if (e < jmb.getPdbCount() - 1)
+          {
+            sb.append(",");
+          }
+
+          if (e == jmb.getPdbCount() - 1 || sb.length() > 20)
+          {
+            lines++;
+            g.drawString(sb.toString(), 20, currentSize.height / 2
+                    - lines * g.getFontMetrics().getHeight());
+          }
+        }
       }
-      else if(fileLoadingError!=null)
+      else if (jmb == null || jmb.jmolViewer == null || !jmb.isFinishedInit())
       {
         g.setColor(Color.black);
         g.fillRect(0, 0, currentSize.width, currentSize.height);
         g.setColor(Color.white);
         g.setFont(new Font("Verdana", Font.BOLD, 14));
-        g.drawString("Error loading file..." + pdbentry.getId(), 20,
-                     currentSize.height / 2);
+        g.drawString(MessageManager.getString("label.retrieving_pdb_data"),
+                20, currentSize.height / 2);
       }
       else
       {
-        viewer.renderScreenImage(g, currentSize, rectClip);
+        jmb.jmolViewer.renderScreenImage(g, currentSize.width,
+                currentSize.height);
       }
     }
   }
 
+  @Override
+  public AAStructureBindingModel getBinding()
+  {
+    return this.jmb;
+  }
+
+  @Override
+  public ViewerType getViewerType()
+  {
+    return ViewerType.JMOL;
+  }
+
+  @Override
+  protected String getViewerName()
+  {
+    return "Jmol";
+  }
 }