Jalview-JS/JAL-3253-applet also comments relating to JAL-3268
[jalview.git] / src / jalview / gui / StructureViewer.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.gui;
22
23 import jalview.api.structures.JalviewStructureDisplayI;
24 import jalview.bin.Cache;
25 import jalview.datamodel.PDBEntry;
26 import jalview.datamodel.SequenceI;
27 import jalview.datamodel.StructureViewerModel;
28 import jalview.structure.StructureMapping;
29 import jalview.structure.StructureSelectionManager;
30 import jalview.util.MessageManager;
31 import jalview.util.Platform;
32 import jalview.ws.DBRefFetcher;
33 import jalview.ws.sifts.SiftsSettings;
34
35 import java.awt.Rectangle;
36 import java.util.ArrayList;
37 import java.util.HashMap;
38 import java.util.LinkedHashMap;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.Map.Entry;
42
43 /**
44  * A proxy for handling structure viewers, that orchestrates adding selected
45  * structures, associated with sequences in Jalview, to an existing viewer, or
46  * opening a new one. Currently supports either Jmol or Chimera as the structure
47  * viewer.
48  * 
49  * @author jprocter
50  */
51 public class StructureViewer
52 {
53
54   static
55   {
56     Platform.loadStaticResource("core/core_jvjmol.z.js",
57             "org.jmol.viewer.Viewer");
58   }
59
60   private static final String UNKNOWN_VIEWER_TYPE = "Unknown structure viewer type ";
61
62   StructureSelectionManager ssm;
63
64   /**
65    * decide if new structures are aligned to existing ones
66    */
67   private boolean superposeAdded = true;
68
69   public enum ViewerType
70   {
71     JMOL, CHIMERA
72   };
73
74   /**
75    * Constructor
76    * 
77    * @param structureSelectionManager
78    */
79   public StructureViewer(StructureSelectionManager structureSelectionManager)
80   {
81     ssm = structureSelectionManager;
82   }
83
84   /**
85    * Factory to create a proxy for modifying existing structure viewer
86    * 
87    */
88   public static StructureViewer reconfigure(
89           JalviewStructureDisplayI display)
90   {
91     StructureViewer sv = new StructureViewer(display.getBinding().getSsm());
92     sv.sview = display;
93     return sv;
94   }
95
96   @Override
97   public String toString()
98   {
99     if (sview != null)
100     {
101       return sview.toString();
102     }
103     return "New View";
104   }
105   public ViewerType getViewerType()
106   {
107     String viewType = Cache.getDefault(Preferences.STRUCTURE_DISPLAY,
108             ViewerType.JMOL.name());
109     return ViewerType.valueOf(viewType);
110   }
111
112   public void setViewerType(ViewerType type)
113   {
114     Cache.setProperty(Preferences.STRUCTURE_DISPLAY, type.name());
115   }
116
117   /**
118    * View multiple PDB entries, each with associated sequences
119    * 
120    * @param pdbs
121    * @param seqs
122    * @param ap
123    * @return
124    */
125   public JalviewStructureDisplayI viewStructures(PDBEntry[] pdbs,
126           SequenceI[] seqs, AlignmentPanel ap)
127   {
128     JalviewStructureDisplayI viewer = onlyOnePdb(pdbs, seqs, ap);
129     if (viewer != null)
130     {
131       /*
132        * user added structure to an existing viewer - all done
133        */
134       return viewer;
135     }
136
137     ViewerType viewerType = getViewerType();
138
139     Map<PDBEntry, SequenceI[]> seqsForPdbs = getSequencesForPdbs(pdbs,
140             seqs);
141     PDBEntry[] pdbsForFile = seqsForPdbs.keySet().toArray(
142             new PDBEntry[seqsForPdbs.size()]);
143     SequenceI[][] theSeqs = seqsForPdbs.values().toArray(
144             new SequenceI[seqsForPdbs.size()][]);
145     if (sview != null)
146     {
147       sview.setAlignAddedStructures(superposeAdded);
148       new Thread(new Runnable()
149       {
150         @Override
151         public void run()
152         {
153
154           for (int pdbep = 0; pdbep < pdbsForFile.length; pdbep++)
155           {
156             PDBEntry pdb = pdbsForFile[pdbep];
157             if (!sview.addAlreadyLoadedFile(theSeqs[pdbep], null, ap,
158                     pdb.getId()))
159             {
160               sview.addToExistingViewer(pdb, theSeqs[pdbep], null, ap,
161                       pdb.getId());
162             }
163           }
164
165           sview.updateTitleAndMenus();
166         }
167       }).start();
168       return sview;
169     }
170
171     if (viewerType.equals(ViewerType.JMOL))
172     {
173       sview = new AppJmol(ap, superposeAdded, pdbsForFile, theSeqs);
174     }
175     else if (viewerType.equals(ViewerType.CHIMERA))
176     {
177       sview = new ChimeraViewFrame(pdbsForFile, superposeAdded, theSeqs,
178               ap);
179     }
180     else
181     {
182       Cache.log.error(UNKNOWN_VIEWER_TYPE + getViewerType().toString());
183     }
184     return sview;
185   }
186
187   /**
188    * Converts the list of selected PDB entries (possibly including duplicates
189    * for multiple chains), and corresponding sequences, into a map of sequences
190    * for each distinct PDB file. Returns null if either argument is null, or
191    * their lengths do not match.
192    * 
193    * @param pdbs
194    * @param seqs
195    * @return
196    */
197   Map<PDBEntry, SequenceI[]> getSequencesForPdbs(PDBEntry[] pdbs,
198           SequenceI[] seqs)
199   {
200     if (pdbs == null || seqs == null || pdbs.length != seqs.length)
201     {
202       return null;
203     }
204
205     /*
206      * we want only one 'representative' PDBEntry per distinct file name
207      * (there may be entries for distinct chains)
208      */
209     Map<String, PDBEntry> pdbsSeen = new HashMap<>();
210
211     /*
212      * LinkedHashMap preserves order of PDB entries (significant if they
213      * will get superimposed to the first structure)
214      */
215     Map<PDBEntry, List<SequenceI>> pdbSeqs = new LinkedHashMap<>();
216     for (int i = 0; i < pdbs.length; i++)
217     {
218       PDBEntry pdb = pdbs[i];
219       SequenceI seq = seqs[i];
220       String pdbFile = pdb.getFile();
221       if (pdbFile == null || pdbFile.length() == 0)
222       {
223         pdbFile = pdb.getId();
224       }
225       if (!pdbsSeen.containsKey(pdbFile))
226       {
227         pdbsSeen.put(pdbFile, pdb);
228         pdbSeqs.put(pdb, new ArrayList<SequenceI>());
229       }
230       else
231       {
232         pdb = pdbsSeen.get(pdbFile);
233       }
234       List<SequenceI> seqsForPdb = pdbSeqs.get(pdb);
235       if (!seqsForPdb.contains(seq))
236       {
237         seqsForPdb.add(seq);
238       }
239     }
240
241     /*
242      * convert to Map<PDBEntry, SequenceI[]>
243      */
244     Map<PDBEntry, SequenceI[]> result = new LinkedHashMap<>();
245     for (Entry<PDBEntry, List<SequenceI>> entry : pdbSeqs.entrySet())
246     {
247       List<SequenceI> theSeqs = entry.getValue();
248       result.put(entry.getKey(),
249               theSeqs.toArray(new SequenceI[theSeqs.size()]));
250     }
251
252     return result;
253   }
254
255   /**
256    * A strictly temporary method pending JAL-1761 refactoring. Determines if all
257    * the passed PDB entries are the same (this is the case if selected sequences
258    * to view structure for are chains of the same structure). If so, calls the
259    * single-pdb version of viewStructures and returns the viewer, else returns
260    * null.
261    * 
262    * @param pdbs
263    * @param seqsForPdbs
264    * @param ap
265    * @return
266    */
267   private JalviewStructureDisplayI onlyOnePdb(PDBEntry[] pdbs,
268           SequenceI[] seqsForPdbs, AlignmentPanel ap)
269   {
270     List<SequenceI> seqs = new ArrayList<>();
271     if (pdbs == null || pdbs.length == 0)
272     {
273       return null;
274     }
275     int i = 0;
276     String firstFile = pdbs[0].getFile();
277     for (PDBEntry pdb : pdbs)
278     {
279       String pdbFile = pdb.getFile();
280       if (pdbFile == null || !pdbFile.equals(firstFile))
281       {
282         return null;
283       }
284       SequenceI pdbseq = seqsForPdbs[i++];
285       if (pdbseq != null)
286       {
287         seqs.add(pdbseq);
288       }
289     }
290     return viewStructures(pdbs[0], seqs.toArray(new SequenceI[seqs.size()]),
291             ap);
292   }
293
294   JalviewStructureDisplayI sview = null;
295
296   public JalviewStructureDisplayI viewStructures(PDBEntry pdb,
297           SequenceI[] seqsForPdb, AlignmentPanel ap)
298   {
299     if (sview != null)
300     {
301       sview.setAlignAddedStructures(superposeAdded);
302       String pdbId = pdb.getId();
303       if (!sview.addAlreadyLoadedFile(seqsForPdb, null, ap, pdbId))
304       {
305         sview.addToExistingViewer(pdb, seqsForPdb, null, ap, pdbId);
306       }
307       sview.updateTitleAndMenus();
308       sview.raiseViewer();
309       return sview;
310     }
311     ViewerType viewerType = getViewerType();
312     if (viewerType.equals(ViewerType.JMOL))
313     {
314       sview = new AppJmol(pdb, seqsForPdb, null, ap);
315     }
316     else if (viewerType.equals(ViewerType.CHIMERA))
317     {
318       sview = new ChimeraViewFrame(pdb, seqsForPdb, null, ap);
319     }
320     else
321     {
322       Cache.log.error(UNKNOWN_VIEWER_TYPE + getViewerType().toString());
323     }
324     return sview;
325   }
326
327   /**
328    * Create a new panel controlling a structure viewer.
329    * 
330    * @param type
331    * @param pdbf
332    * @param id
333    * @param sq
334    * @param alignPanel
335    * @param viewerData
336    * @param fileloc
337    * @param rect
338    * @param vid
339    * @return
340    */
341   public JalviewStructureDisplayI createView(ViewerType type, String[] pdbf,
342           String[] id, SequenceI[][] sq, AlignmentPanel alignPanel,
343           StructureViewerModel viewerData, String fileloc, Rectangle rect,
344           String vid)
345   {
346     final boolean useinViewerSuperpos = viewerData.isAlignWithPanel();
347     final boolean usetoColourbyseq = viewerData.isColourWithAlignPanel();
348     final boolean viewerColouring = viewerData.isColourByViewer();
349
350     switch (type)
351     {
352     case JMOL:
353       sview = new AppJmol(pdbf, id, sq, alignPanel, usetoColourbyseq,
354               useinViewerSuperpos, viewerColouring, fileloc, rect, vid);
355       break;
356     case CHIMERA:
357       Cache.log.error(
358               "Unsupported structure viewer type " + type.toString());
359       break;
360     default:
361       Cache.log.error(UNKNOWN_VIEWER_TYPE + type.toString());
362     }
363     return sview;
364   }
365
366   public boolean isBusy()
367   {
368     if (sview != null)
369     {
370       if (!sview.hasMapping())
371       {
372         return true;
373       }
374     }
375     return false;
376   }
377
378   /**
379    * 
380    * @param pDBid
381    * @return true if view is already showing PDBid
382    */
383   public boolean hasPdbId(String pDBid)
384   {
385     if (sview == null)
386     {
387       return false;
388     }
389
390     return sview.getBinding().hasPdbId(pDBid);
391   }
392
393   public boolean isVisible()
394   {
395     return sview != null && sview.isVisible();
396   }
397
398   public void setSuperpose(boolean alignAddedStructures)
399   {
400     superposeAdded = alignAddedStructures;
401   }
402
403   /**
404    * Launch a minimal implementation of a StructureViewer.
405    * 
406    * @param alignPanel
407    * @param pdb
408    * @param seqs
409    * @return
410    */
411   public static StructureViewer launchStructureViewer(
412           AlignmentPanel alignPanel, PDBEntry pdb, SequenceI[] seqs)
413   {
414     return launchStructureViewer(alignPanel, new PDBEntry[] { pdb }, seqs,
415             false, null, null);
416   }
417
418   /**
419    * Launch a structure viewer with or without an open StructureChooser.
420    * 
421    * Moved from StructureChooser to enable JalviewJS startup with structure
422    * display.
423    * 
424    * @param ap
425    * @param pdbEntriesToView
426    * @param sequences
427    * @param superimpose
428    * @param theViewer
429    * @param pb
430    * @return
431    */
432   protected static StructureViewer launchStructureViewer(
433           final AlignmentPanel ap,
434           final PDBEntry[] pdbEntriesToView, SequenceI[] sequences,
435           boolean superimpose, StructureViewer theViewer,
436           IProgressIndicator pb)
437   {
438     final StructureSelectionManager ssm = ap.getStructureSelectionManager();
439     long progressId = sequences.hashCode();
440     if (pb != null)
441     {
442       pb.setProgressBar(MessageManager
443             .getString("status.launching_3d_structure_viewer"), progressId);
444     }
445     if (theViewer == null)
446     {
447       theViewer = new StructureViewer(ssm);
448     }
449     theViewer.setSuperpose(superimpose);
450   
451     if (pb != null)
452     {
453       pb.setProgressBar(null, progressId);
454     }
455     if (SiftsSettings.isMapWithSifts())
456     {
457       List<SequenceI> seqsWithoutSourceDBRef = new ArrayList<>();
458       int p = 0;
459       // TODO: skip PDBEntry:Sequence pairs where PDBEntry doesn't look like a
460       // real PDB ID. For moment, we can also safely do this if there is already
461       // a known mapping between the PDBEntry and the sequence.
462       for (SequenceI seq : sequences)
463       {
464         PDBEntry pdbe = pdbEntriesToView[p++];
465         if (pdbe != null && pdbe.getFile() != null)
466         {
467           StructureMapping[] smm = ssm.getMapping(pdbe.getFile());
468           if (smm != null && smm.length > 0)
469           {
470             for (StructureMapping sm : smm)
471             {
472               if (sm.getSequence() == seq)
473               {
474                 continue;
475               }
476             }
477           }
478         }
479         if (seq.getPrimaryDBRefs().isEmpty())
480         {
481           seqsWithoutSourceDBRef.add(seq);
482           continue;
483         }
484       }
485       if (!seqsWithoutSourceDBRef.isEmpty())
486       {
487         int y = seqsWithoutSourceDBRef.size();
488         if (pb != null)
489         {
490           pb.setProgressBar(MessageManager.formatMessage(
491                 "status.fetching_dbrefs_for_sequences_without_valid_refs",
492                 y), progressId);
493         }
494         SequenceI[] seqWithoutSrcDBRef = seqsWithoutSourceDBRef
495                 .toArray(new SequenceI[y]);
496         DBRefFetcher dbRefFetcher = new DBRefFetcher(seqWithoutSrcDBRef);
497         dbRefFetcher.fetchDBRefs(true);
498   
499         if (pb != null)
500          {
501           pb.setProgressBar("Fetch complete.", progressId); // todo i18n
502         }
503       }
504     }
505     if (pdbEntriesToView.length > 1)
506     {
507       if (pb != null)
508       {
509         pb.setProgressBar(MessageManager.getString(
510               "status.fetching_3d_structures_for_selected_entries"),
511               progressId);
512       }
513       theViewer.viewStructures(pdbEntriesToView, sequences, ap);
514     }
515     else
516     {
517       if (pb != null)
518       {
519         pb.setProgressBar(MessageManager.formatMessage(
520               "status.fetching_3d_structures_for",
521               pdbEntriesToView[0].getId()),progressId);
522       }
523       theViewer.viewStructures(pdbEntriesToView[0], sequences, ap);
524     }
525     if (pb != null)
526     {
527       pb.setProgressBar(null, progressId);
528     }
529     // remember the last viewer we used...
530     Desktop.getInstance().lastTargetedView = theViewer;
531     return theViewer;
532   }
533
534 }