JAL-3509 coerce RESNUM to hidden (really) after fetching from PDB - resolved conflict...
[jalview.git] / src / jalview / ext / jmol / JalviewJmolBinding.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.ext.jmol;
22
23 import java.awt.Color;
24 import java.awt.Container;
25 import java.awt.event.ComponentEvent;
26 import java.awt.event.ComponentListener;
27 import java.io.File;
28 import java.net.URL;
29 import java.util.ArrayList;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.StringTokenizer;
33 import java.util.Vector;
34
35 import org.jmol.adapter.smarter.SmarterJmolAdapter;
36 import org.jmol.api.JmolAppConsoleInterface;
37 import org.jmol.api.JmolSelectionListener;
38 import org.jmol.api.JmolStatusListener;
39 import org.jmol.api.JmolViewer;
40 import org.jmol.c.CBK;
41 import org.jmol.viewer.Viewer;
42
43 import jalview.api.AlignmentViewPanel;
44 import jalview.api.FeatureRenderer;
45 import jalview.api.FeatureSettingsModelI;
46 import jalview.api.SequenceRenderer;
47 import jalview.bin.Cache;
48 import jalview.datamodel.PDBEntry;
49 import jalview.datamodel.SequenceI;
50 import jalview.gui.AppJmol;
51 import jalview.gui.IProgressIndicator;
52 import jalview.gui.StructureViewer.ViewerType;
53 import jalview.io.DataSourceType;
54 import jalview.io.StructureFile;
55 import jalview.structure.AtomSpec;
56 import jalview.structure.StructureCommand;
57 import jalview.structure.StructureCommandI;
58 import jalview.structure.StructureSelectionManager;
59 import jalview.structures.models.AAStructureBindingModel;
60 import jalview.ws.dbsources.Pdb;
61 import javajs.util.BS;
62
63 public abstract class JalviewJmolBinding extends AAStructureBindingModel
64         implements JmolStatusListener, JmolSelectionListener,
65         ComponentListener
66 {
67   private String lastMessage;
68
69   /*
70    * when true, try to search the associated datamodel for sequences that are
71    * associated with any unknown structures in the Jmol view.
72    */
73   private boolean associateNewStructs = false;
74
75   private Vector<String> atomsPicked = new Vector<>();
76
77   private String lastCommand;
78
79   private boolean loadedInline;
80
81   private StringBuffer resetLastRes = new StringBuffer();
82
83   public Viewer jmolViewer;
84
85   public JalviewJmolBinding(StructureSelectionManager ssm,
86           PDBEntry[] pdbentry, SequenceI[][] sequenceIs,
87           DataSourceType protocol)
88   {
89     super(ssm, pdbentry, sequenceIs, protocol);
90     setStructureCommands(new JmolCommands());
91     /*
92      * viewer = JmolViewer.allocateViewer(renderPanel, new SmarterJmolAdapter(),
93      * "jalviewJmol", ap.av.applet .getDocumentBase(),
94      * ap.av.applet.getCodeBase(), "", this);
95      * 
96      * jmolpopup = JmolPopup.newJmolPopup(viewer, true, "Jmol", true);
97      */
98   }
99
100   public JalviewJmolBinding(StructureSelectionManager ssm,
101           SequenceI[][] seqs, Viewer theViewer)
102   {
103     super(ssm, seqs);
104
105     jmolViewer = theViewer;
106     jmolViewer.setJmolStatusListener(this);
107     jmolViewer.addSelectionListener(this);
108     setStructureCommands(new JmolCommands());
109   }
110
111   /**
112    * construct a title string for the viewer window based on the data jalview
113    * knows about
114    * 
115    * @return
116    */
117   public String getViewerTitle()
118   {
119     return getViewerTitle("Jmol", true);
120   }
121
122   private String jmolScript(String script)
123   {
124     Cache.log.debug(">>Jmol>> " + script);
125     String s = jmolViewer.evalStringQuiet(script); // scriptWait(script); BH
126     Cache.log.debug("<<Jmol<< " + s);
127
128     return s;
129   }
130
131   @Override
132   public List<String> executeCommand(StructureCommandI command,
133           boolean getReply)
134   {
135     if (command == null)
136     {
137       return null;
138     }
139     String cmd = command.getCommand();
140     jmolHistory(false);
141     if (lastCommand == null || !lastCommand.equals(cmd))
142     {
143       jmolScript(cmd + "\n");
144     }
145     jmolHistory(true);
146     lastCommand = cmd;
147     return null;
148   }
149
150   public void createImage(String file, String type, int quality)
151   {
152     System.out.println("JMOL CREATE IMAGE");
153   }
154
155   @Override
156   public String createImage(String fileName, String type,
157           Object textOrBytes, int quality)
158   {
159     System.out.println("JMOL CREATE IMAGE");
160     return null;
161   }
162
163   @Override
164   public String eval(String strEval)
165   {
166     // System.out.println(strEval);
167     // "# 'eval' is implemented only for the applet.";
168     return null;
169   }
170
171   // End StructureListener
172   // //////////////////////////
173
174   @Override
175   public float[][] functionXY(String functionName, int x, int y)
176   {
177     return null;
178   }
179
180   @Override
181   public float[][][] functionXYZ(String functionName, int nx, int ny,
182           int nz)
183   {
184     // TODO Auto-generated method stub
185     return null;
186   }
187
188   /**
189    * map between index of model filename returned from getPdbFile and the first
190    * index of models from this file in the viewer. Note - this is not trimmed -
191    * use getPdbFile to get number of unique models.
192    */
193   private int _modelFileNameMap[];
194
195   @Override
196   public synchronized String[] getStructureFiles()
197   {
198     if (jmolViewer == null)
199     {
200       return new String[0];
201     }
202
203     if (modelFileNames == null)
204     {
205       int modelCount = jmolViewer.ms.mc;
206       String filePath = null;
207       List<String> mset = new ArrayList<>();
208       for (int i = 0; i < modelCount; ++i)
209       {
210         /*
211          * defensive check for null as getModelFileName can return null
212          * even when model count ms.mc is > 0
213          */
214         filePath = jmolViewer.ms.getModelFileName(i);
215         if (filePath != null && !mset.contains(filePath))
216         {
217           mset.add(filePath);
218         }
219       }
220       if (!mset.isEmpty())
221       {
222         modelFileNames = mset.toArray(new String[mset.size()]);
223       }
224     }
225
226     return modelFileNames;
227   }
228
229   /**
230    * map from string to applet
231    */
232   @Override
233   public Map<String, Object> getRegistryInfo()
234   {
235     // TODO Auto-generated method stub
236     return null;
237   }
238
239   // ///////////////////////////////
240   // JmolStatusListener
241
242   public void handlePopupMenu(int x, int y)
243   {
244     // jmolpopup.show(x, y);
245     // jmolpopup.jpiShow(x, y);
246   }
247
248   /**
249    * Highlight zero, one or more atoms on the structure
250    */
251   @Override
252   public void highlightAtoms(List<AtomSpec> atoms)
253   {
254     if (atoms != null)
255     {
256       if (resetLastRes.length() > 0)
257       {
258         jmolScript(resetLastRes.toString());
259         resetLastRes.setLength(0);
260       }
261       for (AtomSpec atom : atoms)
262       {
263         highlightAtom(atom.getAtomIndex(), atom.getPdbResNum(),
264                 atom.getChain(), atom.getPdbFile());
265       }
266     }
267   }
268
269   // jmol/ssm only
270   public void highlightAtom(int atomIndex, int pdbResNum, String chain,
271           String pdbfile)
272   {
273     String modelId = getModelIdForFile(pdbfile);
274     if (modelId.isEmpty())
275     {
276       return;
277     }
278
279     jmolHistory(false);
280
281     StringBuilder selection = new StringBuilder(32);
282     StringBuilder cmd = new StringBuilder(64);
283     selection.append("select ").append(String.valueOf(pdbResNum));
284     selection.append(":");
285     if (!chain.equals(" "))
286     {
287       selection.append(chain);
288     }
289     selection.append(" /").append(modelId);
290
291     cmd.append(selection).append(";wireframe 100;").append(selection)
292             .append(" and not hetero;").append("spacefill 200;select none");
293
294     resetLastRes.append(selection).append(";wireframe 0;").append(selection)
295             .append(" and not hetero; spacefill 0;");
296
297     jmolScript(cmd.toString());
298     jmolHistory(true);
299   }
300
301   private boolean debug = true;
302
303   private void jmolHistory(boolean enable)
304   {
305     jmolScript("History " + ((debug || enable) ? "on" : "off"));
306   }
307
308   public void loadInline(String string)
309   {
310     loadedInline = true;
311     // TODO: re JAL-623
312     // viewer.loadInline(strModel, isAppend);
313     // could do this:
314     // construct fake fullPathName and fileName so we can identify the file
315     // later.
316     // Then, construct pass a reader for the string to Jmol.
317     // ((org.jmol.Viewer.Viewer) viewer).loadModelFromFile(fullPathName,
318     // fileName, null, reader, false, null, null, 0);
319     jmolViewer.openStringInline(string);
320   }
321
322   protected void mouseOverStructure(int atomIndex, final String strInfo)
323   {
324     int pdbResNum;
325     int alocsep = strInfo.indexOf("^");
326     int mdlSep = strInfo.indexOf("/");
327     int chainSeparator = strInfo.indexOf(":"), chainSeparator1 = -1;
328
329     if (chainSeparator == -1)
330     {
331       chainSeparator = strInfo.indexOf(".");
332       if (mdlSep > -1 && mdlSep < chainSeparator)
333       {
334         chainSeparator1 = chainSeparator;
335         chainSeparator = mdlSep;
336       }
337     }
338     // handle insertion codes
339     if (alocsep != -1)
340     {
341       pdbResNum = Integer.parseInt(
342               strInfo.substring(strInfo.indexOf("]") + 1, alocsep));
343
344     }
345     else
346     {
347       pdbResNum = Integer.parseInt(
348               strInfo.substring(strInfo.indexOf("]") + 1, chainSeparator));
349     }
350     String chainId;
351
352     if (strInfo.indexOf(":") > -1)
353     {
354       chainId = strInfo.substring(strInfo.indexOf(":") + 1,
355               strInfo.indexOf("."));
356     }
357     else
358     {
359       chainId = " ";
360     }
361
362     String pdbfilename = modelFileNames[0]; // default is first model
363     if (mdlSep > -1)
364     {
365       if (chainSeparator1 == -1)
366       {
367         chainSeparator1 = strInfo.indexOf(".", mdlSep);
368       }
369       String mdlId = (chainSeparator1 > -1)
370               ? strInfo.substring(mdlSep + 1, chainSeparator1)
371               : strInfo.substring(mdlSep + 1);
372       try
373       {
374         // recover PDB filename for the model hovered over.
375         int mnumber = Integer.valueOf(mdlId).intValue() - 1;
376         if (_modelFileNameMap != null)
377         {
378           int _mp = _modelFileNameMap.length - 1;
379
380           while (mnumber < _modelFileNameMap[_mp])
381           {
382             _mp--;
383           }
384           pdbfilename = modelFileNames[_mp];
385         }
386         else
387         {
388           if (mnumber >= 0 && mnumber < modelFileNames.length)
389           {
390             pdbfilename = modelFileNames[mnumber];
391           }
392
393           if (pdbfilename == null)
394           {
395             pdbfilename = new File(jmolViewer.ms.getModelFileName(mnumber))
396                     .getAbsolutePath();
397           }
398         }
399       } catch (Exception e)
400       {
401       }
402     }
403
404     /*
405      * highlight position on alignment(s); if some text is returned, 
406      * show this as a second line on the structure hover tooltip
407      */
408     String label = getSsm().mouseOverStructure(pdbResNum, chainId,
409             pdbfilename);
410     if (label != null)
411     {
412       // change comma to pipe separator (newline token for Jmol)
413       label = label.replace(',', '|');
414       StringTokenizer toks = new StringTokenizer(strInfo, " ");
415       StringBuilder sb = new StringBuilder();
416       sb.append("select ").append(String.valueOf(pdbResNum)).append(":")
417               .append(chainId).append("/1");
418       sb.append(";set hoverLabel \"").append(toks.nextToken()).append(" ")
419               .append(toks.nextToken());
420       sb.append("|").append(label).append("\"");
421       executeCommand(new StructureCommand(sb.toString()), false);
422     }
423   }
424
425   public void notifyAtomHovered(int atomIndex, String strInfo, String data)
426   {
427     if (strInfo.equals(lastMessage))
428     {
429       return;
430     }
431     lastMessage = strInfo;
432     if (data != null)
433     {
434       System.err.println("Ignoring additional hover info: " + data
435               + " (other info: '" + strInfo + "' pos " + atomIndex + ")");
436     }
437     mouseOverStructure(atomIndex, strInfo);
438   }
439
440   /*
441    * { if (history != null && strStatus != null &&
442    * !strStatus.equals("Script completed")) { history.append("\n" + strStatus);
443    * } }
444    */
445
446   public void notifyAtomPicked(int atomIndex, String strInfo,
447           String strData)
448   {
449     /**
450      * this implements the toggle label behaviour copied from the original
451      * structure viewer, mc_view
452      */
453     if (strData != null)
454     {
455       System.err.println("Ignoring additional pick data string " + strData);
456     }
457     int chainSeparator = strInfo.indexOf(":");
458     int p = 0;
459     if (chainSeparator == -1)
460     {
461       chainSeparator = strInfo.indexOf(".");
462     }
463
464     String picked = strInfo.substring(strInfo.indexOf("]") + 1,
465             chainSeparator);
466     String mdlString = "";
467     if ((p = strInfo.indexOf(":")) > -1)
468     {
469       picked += strInfo.substring(p, strInfo.indexOf("."));
470     }
471
472     if ((p = strInfo.indexOf("/")) > -1)
473     {
474       mdlString += strInfo.substring(p, strInfo.indexOf(" #"));
475     }
476     picked = "((" + picked + ".CA" + mdlString + ")|(" + picked + ".P"
477             + mdlString + "))";
478     jmolHistory(false);
479
480     if (!atomsPicked.contains(picked))
481     {
482       jmolScript("select " + picked + ";label %n %r:%c");
483       atomsPicked.addElement(picked);
484     }
485     else
486     {
487       jmolViewer.evalString("select " + picked + ";label off");
488       atomsPicked.removeElement(picked);
489     }
490     jmolHistory(true);
491     // TODO: in application this happens
492     //
493     // if (scriptWindow != null)
494     // {
495     // scriptWindow.sendConsoleMessage(strInfo);
496     // scriptWindow.sendConsoleMessage("\n");
497     // }
498
499   }
500
501   @Override
502   public void notifyCallback(CBK type, Object[] data)
503   {
504     try
505     {
506       switch (type)
507       {
508       case LOADSTRUCT:
509         notifyFileLoaded((String) data[1], (String) data[2],
510                 (String) data[3], (String) data[4],
511                 ((Integer) data[5]).intValue());
512
513         break;
514       case PICK:
515         notifyAtomPicked(((Integer) data[2]).intValue(), (String) data[1],
516                 (String) data[0]);
517         // also highlight in alignment
518         // deliberate fall through
519       case HOVER:
520         notifyAtomHovered(((Integer) data[2]).intValue(), (String) data[1],
521                 (String) data[0]);
522         break;
523       case SCRIPT:
524         notifyScriptTermination((String) data[2],
525                 ((Integer) data[3]).intValue());
526         break;
527       case ECHO:
528         sendConsoleEcho((String) data[1]);
529         break;
530       case MESSAGE:
531         sendConsoleMessage(
532                 (data == null) ? ((String) null) : (String) data[1]);
533         break;
534       case ERROR:
535         // System.err.println("Ignoring error callback.");
536         break;
537       case SYNC:
538       case RESIZE:
539         refreshGUI();
540         break;
541       case MEASURE:
542
543       case CLICK:
544       default:
545         System.err.println(
546                 "Unhandled callback " + type + " " + data[1].toString());
547         break;
548       }
549     } catch (Exception e)
550     {
551       System.err.println("Squashed Jmol callback handler error:");
552       e.printStackTrace();
553     }
554   }
555
556   @Override
557   public boolean notifyEnabled(CBK callbackPick)
558   {
559     switch (callbackPick)
560     {
561     case ECHO:
562     case LOADSTRUCT:
563     case MEASURE:
564     case MESSAGE:
565     case PICK:
566     case SCRIPT:
567     case HOVER:
568     case ERROR:
569       return true;
570     default:
571       return false;
572     }
573   }
574
575   // incremented every time a load notification is successfully handled -
576   // lightweight mechanism for other threads to detect when they can start
577   // referrring to new structures.
578   private long loadNotifiesHandled = 0;
579
580   public long getLoadNotifiesHandled()
581   {
582     return loadNotifiesHandled;
583   }
584
585   public void notifyFileLoaded(String fullPathName, String fileName2,
586           String modelName, String errorMsg, int modelParts)
587   {
588     if (errorMsg != null)
589     {
590       fileLoadingError = errorMsg;
591       refreshGUI();
592       return;
593     }
594     // TODO: deal sensibly with models loaded inLine:
595     // modelName will be null, as will fullPathName.
596
597     // the rest of this routine ignores the arguments, and simply interrogates
598     // the Jmol view to find out what structures it contains, and adds them to
599     // the structure selection manager.
600     fileLoadingError = null;
601     String[] oldmodels = modelFileNames;
602     modelFileNames = null;
603     boolean notifyLoaded = false;
604     String[] modelfilenames = getStructureFiles();
605     if (modelfilenames == null)
606     {
607       // Jmol is still loading files!
608       return;
609     }
610     // first check if we've lost any structures
611     if (oldmodels != null && oldmodels.length > 0)
612     {
613       int oldm = 0;
614       for (int i = 0; i < oldmodels.length; i++)
615       {
616         for (int n = 0; n < modelfilenames.length; n++)
617         {
618           if (modelfilenames[n] == oldmodels[i])
619           {
620             oldmodels[i] = null;
621             break;
622           }
623         }
624         if (oldmodels[i] != null)
625         {
626           oldm++;
627         }
628       }
629       if (oldm > 0)
630       {
631         String[] oldmfn = new String[oldm];
632         oldm = 0;
633         for (int i = 0; i < oldmodels.length; i++)
634         {
635           if (oldmodels[i] != null)
636           {
637             oldmfn[oldm++] = oldmodels[i];
638           }
639         }
640         // deregister the Jmol instance for these structures - we'll add
641         // ourselves again at the end for the current structure set.
642         getSsm().removeStructureViewerListener(this, oldmfn);
643       }
644     }
645     refreshPdbEntries();
646     for (int modelnum = 0; modelnum < modelfilenames.length; modelnum++)
647     {
648       String fileName = modelfilenames[modelnum];
649       boolean foundEntry = false;
650       StructureFile pdb = null;
651       String pdbfile = null;
652       // model was probably loaded inline - so check the pdb file hashcode
653       if (loadedInline)
654       {
655         // calculate essential attributes for the pdb data imported inline.
656         // prolly need to resolve modelnumber properly - for now just use our
657         // 'best guess'
658         pdbfile = jmolViewer.getData(
659                 "" + (1 + _modelFileNameMap[modelnum]) + ".0", "PDB");
660       }
661       // search pdbentries and sequences to find correct pdbentry for this
662       // model
663       for (int pe = 0; pe < getPdbCount(); pe++)
664       {
665         boolean matches = false;
666         addSequence(pe, getSequence()[pe]);
667         if (fileName == null)
668         {
669           if (false)
670           // see JAL-623 - need method of matching pasted data up
671           {
672             pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
673                     pdbfile, DataSourceType.PASTE, getIProgressIndicator());
674             getPdbEntry(modelnum).setFile("INLINE" + pdb.getId());
675             matches = true;
676             foundEntry = true;
677           }
678         }
679         else
680         {
681           File fl = new File(getPdbEntry(pe).getFile());
682           matches = fl.equals(new File(fileName));
683           if (matches)
684           {
685             foundEntry = true;
686             // TODO: Jmol can in principle retrieve from CLASSLOADER but
687             // this
688             // needs
689             // to be tested. See mantis bug
690             // https://mantis.lifesci.dundee.ac.uk/view.php?id=36605
691             DataSourceType protocol = DataSourceType.URL;
692             try
693             {
694               if (fl.exists())
695               {
696                 protocol = DataSourceType.FILE;
697               }
698             } catch (Exception e)
699             {
700             } catch (Error e)
701             {
702             }
703             // Explicitly map to the filename used by Jmol ;
704             pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
705                     fileName, protocol, getIProgressIndicator());
706             // pdbentry[pe].getFile(), protocol);
707
708           }
709         }
710         if (matches)
711         {
712           stashFoundChains(pdb, fileName);
713           notifyLoaded = true;
714         }
715       }
716
717       if (!foundEntry && associateNewStructs)
718       {
719         // this is a foreign pdb file that jalview doesn't know about - add
720         // it to the dataset and try to find a home - either on a matching
721         // sequence or as a new sequence.
722         String pdbcontent = jmolViewer.getData("/" + (modelnum + 1) + ".1",
723                 "PDB");
724         // parse pdb file into a chain, etc.
725         // locate best match for pdb in associated views and add mapping to
726         // ssm
727         // if properly registered then
728         notifyLoaded = true;
729
730       }
731     }
732     // FILE LOADED OK
733     // so finally, update the jmol bits and pieces
734     // if (jmolpopup != null)
735     // {
736     // // potential for deadlock here:
737     // // jmolpopup.updateComputedMenus();
738     // }
739     if (!isLoadingFromArchive())
740     {
741       jmolScript(
742               "model *; select backbone;restrict;cartoon;wireframe off;spacefill off");
743     }
744     // register ourselves as a listener and notify the gui that it needs to
745     // update itself.
746     getSsm().addStructureViewerListener(this);
747     if (notifyLoaded)
748     {
749       FeatureRenderer fr = getFeatureRenderer(null);
750       if (fr != null)
751       {
752         // JAL-1842 ensure FeatureRenderer knows about RESNUM
753         fr.featuresAdded();
754         // ... apply feature settings (e.g. to hide RESNUM)
755         FeatureSettingsModelI colours = new Pdb().getFeatureColourScheme();
756         ((AppJmol) getViewer()).getAlignmentPanel().av
757                 .applyFeaturesStyle(colours);
758         // ... and refresh again to ensure FeatureSettings is in synch!
759         fr.notifyFeaturesChanged();
760       }
761       refreshGUI();
762       loadNotifiesHandled++;
763     }
764     setLoadingFromArchive(false);
765   }
766
767   protected IProgressIndicator getIProgressIndicator()
768   {
769     return null;
770   }
771
772   public void notifyNewPickingModeMeasurement(int iatom, String strMeasure)
773   {
774     notifyAtomPicked(iatom, strMeasure, null);
775   }
776
777   public abstract void notifyScriptTermination(String strStatus,
778           int msWalltime);
779
780   /**
781    * display a message echoed from the jmol viewer
782    * 
783    * @param strEcho
784    */
785   public abstract void sendConsoleEcho(String strEcho); /*
786                                                          * { showConsole(true);
787                                                          * 
788                                                          * history.append("\n" +
789                                                          * strEcho); }
790                                                          */
791
792   // /End JmolStatusListener
793   // /////////////////////////////
794
795   /**
796    * @param strStatus
797    *          status message - usually the response received after a script
798    *          executed
799    */
800   public abstract void sendConsoleMessage(String strStatus);
801
802   @Override
803   public void setCallbackFunction(String callbackType,
804           String callbackFunction)
805   {
806     System.err.println("Ignoring set-callback request to associate "
807             + callbackType + " with function " + callbackFunction);
808
809   }
810
811   public void showHelp()
812   {
813     showUrl("http://wiki.jmol.org"
814     // BH 2018 "http://jmol.sourceforge.net/docs/JmolUserGuide/"
815             , "jmolHelp");
816   }
817
818   /**
819    * open the URL somehow
820    * 
821    * @param target
822    */
823   public abstract void showUrl(String url, String target);
824
825   /**
826    * called to show or hide the associated console window container.
827    * 
828    * @param show
829    */
830   public abstract void showConsole(boolean show);
831
832   public static Viewer getJmolData(JmolParser jmolParser)
833   {
834     return (Viewer) JmolViewer.allocateViewer(null, null, null, null, null,
835             "-x -o -n", jmolParser);
836   }
837
838   /**
839    * 
840    * 
841    * 
842    * @param renderPanel
843    * @param jmolfileio
844    *          - when true will initialise jmol's file IO system (should be false
845    *          in applet context)
846    * @param htmlName
847    * @param documentBase
848    * @param codeBase
849    * @param commandOptions
850    */
851   public void allocateViewer(Container renderPanel, boolean jmolfileio,
852           String htmlName, URL documentBase, URL codeBase,
853           String commandOptions)
854   {
855     allocateViewer(renderPanel, jmolfileio, htmlName, documentBase,
856             codeBase, commandOptions, null, null);
857   }
858
859   /**
860    * 
861    * @param renderPanel
862    * @param jmolfileio
863    *          - when true will initialise jmol's file IO system (should be false
864    *          in applet context)
865    * @param htmlName
866    * @param documentBase
867    * @param codeBase
868    * @param commandOptions
869    * @param consolePanel
870    *          - panel to contain Jmol console
871    * @param buttonsToShow
872    *          - buttons to show on the console, in order
873    */
874   public void allocateViewer(Container renderPanel, boolean jmolfileio,
875           String htmlName, URL documentBase, URL codeBase,
876           String commandOptions, final Container consolePanel,
877           String buttonsToShow)
878   {
879
880     System.err.println("Allocating Jmol Viewer: " + commandOptions);
881
882     if (commandOptions == null)
883     {
884       commandOptions = "";
885     }
886     jmolViewer = (Viewer) JmolViewer.allocateViewer(renderPanel,
887             (jmolfileio ? new SmarterJmolAdapter() : null),
888             htmlName + ((Object) this).toString(), documentBase, codeBase,
889             commandOptions, this);
890
891     jmolViewer.setJmolStatusListener(this); // extends JmolCallbackListener
892
893     try
894     {
895       console = createJmolConsole(consolePanel, buttonsToShow);
896     } catch (Throwable e)
897     {
898       System.err.println("Could not create Jmol application console. "
899               + e.getMessage());
900       e.printStackTrace();
901     }
902     if (consolePanel != null)
903     {
904       consolePanel.addComponentListener(this);
905
906     }
907
908   }
909
910   protected abstract JmolAppConsoleInterface createJmolConsole(
911           Container consolePanel, String buttonsToShow);
912
913   // BH 2018 -- Jmol console is not working due to problems with styled
914   // documents.
915
916   protected org.jmol.api.JmolAppConsoleInterface console = null;
917
918   @Override
919   public int[] resizeInnerPanel(String data)
920   {
921     // Jalview doesn't honour resize panel requests
922     return null;
923   }
924
925   /**
926    * 
927    */
928   protected void closeConsole()
929   {
930     if (console != null)
931     {
932       try
933       {
934         console.setVisible(false);
935       } catch (Error e)
936       {
937       } catch (Exception x)
938       {
939       }
940       ;
941       console = null;
942     }
943   }
944
945   /**
946    * ComponentListener method
947    */
948   @Override
949   public void componentMoved(ComponentEvent e)
950   {
951   }
952
953   /**
954    * ComponentListener method
955    */
956   @Override
957   public void componentResized(ComponentEvent e)
958   {
959   }
960
961   /**
962    * ComponentListener method
963    */
964   @Override
965   public void componentShown(ComponentEvent e)
966   {
967     showConsole(true);
968   }
969
970   /**
971    * ComponentListener method
972    */
973   @Override
974   public void componentHidden(ComponentEvent e)
975   {
976     showConsole(false);
977   }
978
979   @Override
980   protected String getModelIdForFile(String pdbFile)
981   {
982     if (modelFileNames == null)
983     {
984       return "";
985     }
986     for (int i = 0; i < modelFileNames.length; i++)
987     {
988       if (modelFileNames[i].equalsIgnoreCase(pdbFile))
989       {
990         return String.valueOf(i + 1);
991       }
992     }
993     return "";
994   }
995
996   @Override
997   protected ViewerType getViewerType()
998   {
999     return ViewerType.JMOL;
1000   }
1001
1002   @Override
1003   protected String getModelId(int pdbfnum, String file)
1004   {
1005     return String.valueOf(pdbfnum + 1);
1006   }
1007
1008   /**
1009    * Returns ".spt" - the Jmol session file extension
1010    * 
1011    * @return
1012    * @see https://chemapps.stolaf.edu/jmol/docs/#writemodel
1013    */
1014   @Override
1015   public String getSessionFileExtension()
1016   {
1017     return ".spt";
1018   }
1019
1020   @Override
1021   public void selectionChanged(BS arg0)
1022   {
1023     // TODO Auto-generated method stub
1024
1025   }
1026
1027   @Override
1028   public SequenceRenderer getSequenceRenderer(AlignmentViewPanel avp)
1029   {
1030     return new jalview.gui.SequenceRenderer(avp.getAlignViewport());
1031   }
1032
1033   @Override
1034   public String getHelpURL()
1035   {
1036     return "http://wiki.jmol.org"; // BH 2018
1037   }
1038 }